Merge branch 'heads/main' into daniel/srp-removal-round-2

This commit is contained in:
Daniel Hougaard
2025-08-05 22:17:01 +04:00
446 changed files with 13524 additions and 3367 deletions
@@ -22,6 +22,7 @@ export type TAuthMode =
orgId: string;
authMethod: AuthMethod;
isMfaVerified?: boolean;
token: AuthModeJwtTokenPayload;
}
| {
authMode: AuthMode.API_KEY;
@@ -30,6 +31,7 @@ export type TAuthMode =
userId: string;
user: TUsers;
orgId: string;
token: string;
}
| {
authMode: AuthMode.SERVICE_TOKEN;
@@ -38,6 +40,7 @@ export type TAuthMode =
serviceTokenId: string;
orgId: string;
authMethod: null;
token: string;
}
| {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN;
@@ -47,6 +50,7 @@ export type TAuthMode =
orgId: string;
authMethod: null;
isInstanceAdmin?: boolean;
token: TIdentityAccessTokenJwtPayload;
}
| {
authMode: AuthMode.SCIM_TOKEN;
@@ -56,7 +60,7 @@ export type TAuthMode =
authMethod: null;
};
const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
export const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
const apiKey = req.headers?.["x-api-key"];
if (apiKey) {
return { authMode: AuthMode.API_KEY, token: apiKey, actor: ActorType.USER } as const;
@@ -133,7 +137,8 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
actor,
orgId: orgId as string,
authMethod: token.authMethod,
isMfaVerified: token.isMfaVerified
isMfaVerified: token.isMfaVerified,
token
};
break;
}
@@ -148,7 +153,8 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
identityId: identity.identityId,
identityName: identity.name,
authMethod: null,
isInstanceAdmin: serverCfg?.adminIdentityIds?.includes(identity.identityId)
isInstanceAdmin: serverCfg?.adminIdentityIds?.includes(identity.identityId),
token
};
if (token?.identityAuth?.oidc) {
requestContext.set("identityAuthInfo", {
@@ -179,7 +185,8 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
serviceToken,
serviceTokenId: serviceToken.id,
actor,
authMethod: null
authMethod: null,
token
};
break;
}
@@ -191,7 +198,8 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
actor,
user,
orgId: "API_KEY", // We set the orgId to an arbitrary value, since we can't link an API key to a specific org. We have to deprecate API keys soon!
authMethod: null
authMethod: null,
token: token as string
};
break;
}
@@ -4,6 +4,8 @@ import { Probot } from "probot";
import { z } from "zod";
import { TBitbucketPushEvent } from "@app/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-types";
import { TGitLabDataSourcePushEventPayload } from "@app/ee/services/secret-scanning-v2/gitlab";
import { GitLabWebHookEvent } from "@app/ee/services/secret-scanning-v2/gitlab/gitlab-secret-scanning-enums";
import { getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger";
import { writeLimit } from "@app/server/config/rateLimiter";
@@ -113,4 +115,36 @@ export const registerSecretScanningV2Webhooks = async (server: FastifyZodProvide
return res.send("ok");
}
});
// gitlab push event webhook
server.route({
method: "POST",
url: "/gitlab",
config: {
rateLimit: writeLimit
},
handler: async (req, res) => {
const event = req.headers["x-gitlab-event"] as GitLabWebHookEvent;
const token = req.headers["x-gitlab-token"] as string;
const dataSourceId = req.headers["x-data-source-id"] as string;
if (event !== GitLabWebHookEvent.Push) {
return res.status(400).send({ message: `Event type not supported: ${event as string}` });
}
if (!token) {
return res.status(401).send({ message: "Unauthorized: Missing token" });
}
if (!dataSourceId) return res.status(400).send({ message: "Data Source ID header is required" });
await server.services.secretScanningV2.gitlab.handlePushEvent({
dataSourceId,
payload: req.body as TGitLabDataSourcePushEventPayload,
token
});
return res.send("ok");
}
});
};
+36 -7
View File
@@ -31,6 +31,8 @@ import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/pro
import { dynamicSecretLeaseDALFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal";
import { dynamicSecretLeaseQueueServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue";
import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
import { eventBusFactory } from "@app/ee/services/event/event-bus-service";
import { sseServiceFactory } from "@app/ee/services/event/event-sse-service";
import { externalKmsDALFactory } from "@app/ee/services/external-kms/external-kms-dal";
import { externalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
@@ -177,6 +179,8 @@ import { identityAccessTokenDALFactory } from "@app/services/identity-access-tok
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { identityAliCloudAuthDALFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-dal";
import { identityAliCloudAuthServiceFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-service";
import { identityAuthTemplateDALFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-dal";
import { identityAuthTemplateServiceFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-service";
import { identityAwsAuthDALFactory } from "@app/services/identity-aws-auth/identity-aws-auth-dal";
import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
import { identityAzureAuthDALFactory } from "@app/services/identity-azure-auth/identity-azure-auth-dal";
@@ -392,6 +396,7 @@ export const registerRoutes = async (
const identityProjectDAL = identityProjectDALFactory(db);
const identityProjectMembershipRoleDAL = identityProjectMembershipRoleDALFactory(db);
const identityProjectAdditionalPrivilegeDAL = identityProjectAdditionalPrivilegeDALFactory(db);
const identityAuthTemplateDAL = identityAuthTemplateDALFactory(db);
const identityTokenAuthDAL = identityTokenAuthDALFactory(db);
const identityUaDAL = identityUaDALFactory(db);
@@ -495,6 +500,9 @@ export const registerRoutes = async (
const projectMicrosoftTeamsConfigDAL = projectMicrosoftTeamsConfigDALFactory(db);
const secretScanningV2DAL = secretScanningV2DALFactory(db);
const eventBusService = eventBusFactory(server.redis);
const sseService = sseServiceFactory(eventBusService, server.redis);
const permissionService = permissionServiceFactory({
permissionDAL,
orgRoleDAL,
@@ -552,7 +560,8 @@ export const registerRoutes = async (
queueService,
projectDAL,
licenseService,
auditLogStreamDAL
auditLogStreamDAL,
eventBusService
});
const auditLogService = auditLogServiceFactory({ auditLogDAL, permissionService, auditLogQueue });
@@ -1450,6 +1459,15 @@ export const registerRoutes = async (
identityMetadataDAL
});
const identityAuthTemplateService = identityAuthTemplateServiceFactory({
identityAuthTemplateDAL,
identityLdapAuthDAL,
permissionService,
kmsService,
licenseService,
auditLogService
});
const identityAccessTokenService = identityAccessTokenServiceFactory({
identityAccessTokenDAL,
identityOrgMembershipDAL,
@@ -1593,7 +1611,8 @@ export const registerRoutes = async (
identityAccessTokenDAL,
identityOrgMembershipDAL,
licenseService,
identityDAL
identityDAL,
identityAuthTemplateDAL
});
const dynamicSecretProviders = buildDynamicSecretProviders({
@@ -1928,7 +1947,8 @@ export const registerRoutes = async (
projectMembershipDAL,
smtpService,
kmsService,
keyStore
keyStore,
appConnectionDAL
});
const secretScanningV2Service = secretScanningV2ServiceFactory({
@@ -1937,7 +1957,8 @@ export const registerRoutes = async (
licenseService,
secretScanningV2DAL,
secretScanningV2Queue,
kmsService
kmsService,
appConnectionDAL
});
// setup the communication with license key server
@@ -1961,6 +1982,7 @@ export const registerRoutes = async (
await kmsService.startService();
await microsoftTeamsService.start();
await dynamicSecretQueueService.init();
await eventBusService.init();
// inject all services
server.decorate<FastifyZodProvider["services"]>("services", {
@@ -1994,6 +2016,7 @@ export const registerRoutes = async (
webhook: webhookService,
serviceToken: serviceTokenService,
identity: identityService,
identityAuthTemplate: identityAuthTemplateService,
identityAccessToken: identityAccessTokenService,
identityProject: identityProjectService,
identityTokenAuth: identityTokenAuthService,
@@ -2067,7 +2090,9 @@ export const registerRoutes = async (
githubOrgSync: githubOrgSyncConfigService,
folderCommit: folderCommitService,
secretScanningV2: secretScanningV2Service,
reminder: reminderService
reminder: reminderService,
bus: eventBusService,
sse: sseService
});
const cronJobs: CronJob[] = [];
@@ -2128,7 +2153,8 @@ export const registerRoutes = async (
inviteOnlySignup: z.boolean().optional(),
redisConfigured: z.boolean().optional(),
secretScanningConfigured: z.boolean().optional(),
samlDefaultOrgSlug: z.string().optional()
samlDefaultOrgSlug: z.string().optional(),
auditLogStorageDisabled: z.boolean().optional()
})
}
},
@@ -2155,7 +2181,8 @@ export const registerRoutes = async (
inviteOnlySignup: Boolean(serverCfg.allowSignUp),
redisConfigured: cfg.isRedisConfigured,
secretScanningConfigured: cfg.isSecretScanningConfigured,
samlDefaultOrgSlug: cfg.samlDefaultOrgSlug
samlDefaultOrgSlug: cfg.samlDefaultOrgSlug,
auditLogStorageDisabled: Boolean(cfg.DISABLE_AUDIT_LOG_STORAGE)
};
}
});
@@ -2183,5 +2210,7 @@ export const registerRoutes = async (
server.addHook("onClose", async () => {
cronJobs.forEach((job) => job.stop());
await telemetryService.flushAll();
await eventBusService.close();
sseService.close();
});
};
@@ -464,6 +464,42 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
method: "DELETE",
url: "/user-management/users",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
userIds: z.string().array()
}),
response: {
200: z.object({
users: UsersSchema.pick({
username: true,
firstName: true,
lastName: true,
email: true,
id: true
}).array()
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
const users = await server.services.superAdmin.deleteUsers(req.body.userIds);
return {
users
};
}
});
server.route({
method: "PATCH",
url: "/user-management/users/:userId/admin-access",
@@ -75,6 +75,10 @@ import {
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
import { MySqlConnectionListItemSchema, SanitizedMySqlConnectionSchema } from "@app/services/app-connection/mysql";
import {
NetlifyConnectionListItemSchema,
SanitizedNetlifyConnectionSchema
} from "@app/services/app-connection/netlify";
import { OktaConnectionListItemSchema, SanitizedOktaConnectionSchema } from "@app/services/app-connection/okta";
import {
PostgresConnectionListItemSchema,
@@ -145,6 +149,7 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedChecklyConnectionSchema.options,
...SanitizedSupabaseConnectionSchema.options,
...SanitizedDigitalOceanConnectionSchema.options,
...SanitizedNetlifyConnectionSchema.options,
...SanitizedOktaConnectionSchema.options
]);
@@ -184,6 +189,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
ChecklyConnectionListItemSchema,
SupabaseConnectionListItemSchema,
DigitalOceanConnectionListItemSchema,
NetlifyConnectionListItemSchema,
OktaConnectionListItemSchema
]);
@@ -46,7 +46,6 @@ export const registerCloudflareConnectionRouter = async (server: FastifyZodProvi
const { connectionId } = req.params;
const projects = await server.services.appConnection.cloudflare.listPagesProjects(connectionId, req.permission);
return projects;
}
});
@@ -73,9 +72,36 @@ export const registerCloudflareConnectionRouter = async (server: FastifyZodProvi
handler: async (req) => {
const { connectionId } = req.params;
const projects = await server.services.appConnection.cloudflare.listWorkersScripts(connectionId, req.permission);
const scripts = await server.services.appConnection.cloudflare.listWorkersScripts(connectionId, req.permission);
return scripts;
}
});
return projects;
server.route({
method: "GET",
url: `/:connectionId/cloudflare-zones`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z
.object({
id: z.string(),
name: z.string()
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const zones = await server.services.appConnection.cloudflare.listZones(connectionId, req.permission);
return zones;
}
});
};
@@ -26,6 +26,7 @@ import { registerHumanitecConnectionRouter } from "./humanitec-connection-router
import { registerLdapConnectionRouter } from "./ldap-connection-router";
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
import { registerMySqlConnectionRouter } from "./mysql-connection-router";
import { registerNetlifyConnectionRouter } from "./netlify-connection-router";
import { registerOktaConnectionRouter } from "./okta-connection-router";
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
import { registerRailwayConnectionRouter } from "./railway-connection-router";
@@ -76,5 +77,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.Checkly]: registerChecklyConnectionRouter,
[AppConnection.Supabase]: registerSupabaseConnectionRouter,
[AppConnection.DigitalOcean]: registerDigitalOceanConnectionRouter,
[AppConnection.Netlify]: registerNetlifyConnectionRouter,
[AppConnection.Okta]: registerOktaConnectionRouter
};
@@ -0,0 +1,87 @@
import { z } from "zod";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateNetlifyConnectionSchema,
SanitizedNetlifyConnectionSchema,
UpdateNetlifyConnectionSchema
} from "@app/services/app-connection/netlify";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerNetlifyConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.Netlify,
server,
sanitizedResponseSchema: SanitizedNetlifyConnectionSchema,
createSchema: CreateNetlifyConnectionSchema,
updateSchema: UpdateNetlifyConnectionSchema
});
// The below endpoints are not exposed and for Infisical App use
server.route({
method: "GET",
url: `/:connectionId/accounts`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z.object({
accounts: z
.object({
name: z.string(),
id: z.string()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const accounts = await server.services.appConnection.netlify.listAccounts(connectionId, req.permission);
return { accounts };
}
});
server.route({
method: "GET",
url: `/:connectionId/accounts/:accountId/sites`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid(),
accountId: z.string()
}),
response: {
200: z.object({
sites: z
.object({
name: z.string(),
id: z.string()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId, accountId } = req.params;
const sites = await server.services.appConnection.netlify.listSites(connectionId, req.permission, accountId);
return { sites };
}
});
};
@@ -0,0 +1,118 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
import { subject } from "@casl/ability";
import { pipeline } from "stream/promises";
import { z } from "zod";
import { ActionProjectType, ProjectType } from "@app/db/schemas";
import { getServerSentEventsHeaders } from "@app/ee/services/event/event-sse-stream";
import { EventRegisterSchema } from "@app/ee/services/event/types";
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, ForbiddenRequestError, RateLimitError } from "@app/lib/errors";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerEventRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/subscribe/project-events",
config: {
rateLimit: readLimit
},
schema: {
body: z.object({
projectId: z.string().trim(),
register: z.array(EventRegisterSchema).max(10)
})
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req, reply) => {
try {
const { sse, permission, identityAccessToken, authToken, license } = req.server.services;
const plan = await license.getPlan(req.auth.orgId);
if (!plan.eventSubscriptions) {
throw new BadRequestError({
message:
"Failed to use event subscriptions due to plan restriction. Upgrade plan to access enterprise event subscriptions."
});
}
const count = await sse.getActiveConnectionsCount(req.body.projectId, req.permission.id);
if (count >= 5) {
throw new RateLimitError({
message: `Too many active connections for project ${req.body.projectId}. Please close some connections before opening a new one.`
});
}
const client = await sse.subscribe({
type: ProjectType.SecretManager,
registered: req.body.register,
async getAuthInfo() {
const ability = await permission.getProjectPermission({
actor: req.auth.actor,
projectId: req.body.projectId,
actionProjectType: ActionProjectType.Any,
actorAuthMethod: req.auth.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId
});
return { permission: ability.permission, actorId: req.permission.id, projectId: req.body.projectId };
},
async onAuthRefresh(info) {
switch (req.auth.authMode) {
case AuthMode.JWT:
await authToken.fnValidateJwtIdentity(req.auth.token);
break;
case AuthMode.IDENTITY_ACCESS_TOKEN:
await identityAccessToken.fnValidateIdentityAccessToken(req.auth.token, req.realIp);
break;
default:
throw new Error("Unsupported authentication method");
}
req.body.register.forEach((r) => {
const allowed = info.permission.can(
ProjectPermissionSecretActions.Subscribe,
subject(ProjectPermissionSub.Secrets, {
environment: r.conditions?.environmentSlug ?? "",
secretPath: r.conditions?.secretPath ?? "/",
eventType: r.event
})
);
if (!allowed) {
throw new ForbiddenRequestError({
name: "PermissionDenied",
message: `You are not allowed to subscribe on secrets`,
details: {
event: r.event,
environmentSlug: r.conditions?.environmentSlug,
secretPath: r.conditions?.secretPath ?? "/"
}
});
}
});
}
});
// Switches to manual response and enable SSE streaming
reply.hijack();
reply.raw.writeHead(200, getServerSentEventsHeaders()).flushHeaders();
reply.raw.on("close", client.abort);
await pipeline(client.stream, reply.raw, { signal: client.signal });
} catch (error) {
if (error instanceof Error && error.name === "AbortError") {
// If the stream is aborted, we don't need to do anything
return;
}
throw error;
}
}
});
};
@@ -200,49 +200,104 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
params: z.object({
identityId: z.string().trim().describe(LDAP_AUTH.ATTACH.identityId)
}),
body: z
.object({
url: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.url),
bindDN: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.bindDN),
bindPass: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.bindPass),
searchBase: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.searchBase),
searchFilter: z
.string()
.trim()
.min(1)
.default("(uid={{username}})")
.refine(isValidLdapFilter, "Invalid LDAP search filter")
.describe(LDAP_AUTH.ATTACH.searchFilter),
allowedFields: AllowedFieldsSchema.array().optional().describe(LDAP_AUTH.ATTACH.allowedFields),
ldapCaCertificate: z.string().trim().optional().describe(LDAP_AUTH.ATTACH.ldapCaCertificate),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
.describe(LDAP_AUTH.ATTACH.accessTokenTrustedIps),
accessTokenTTL: z
.number()
.int()
.min(0)
.max(315360000)
.default(2592000)
.describe(LDAP_AUTH.ATTACH.accessTokenTTL),
accessTokenMaxTTL: z
.number()
.int()
.min(1)
.max(315360000)
.default(2592000)
.describe(LDAP_AUTH.ATTACH.accessTokenMaxTTL),
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit)
})
.refine(
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
"Access Token TTL cannot be greater than Access Token Max TTL."
),
body: z.union([
// Template-based configuration
z
.object({
templateId: z.string().trim().describe(LDAP_AUTH.ATTACH.templateId),
searchFilter: z
.string()
.trim()
.min(1)
.default("(uid={{username}})")
.refine(isValidLdapFilter, "Invalid LDAP search filter")
.describe(LDAP_AUTH.ATTACH.searchFilter),
allowedFields: AllowedFieldsSchema.array().optional().describe(LDAP_AUTH.ATTACH.allowedFields),
ldapCaCertificate: z.string().trim().optional().describe(LDAP_AUTH.ATTACH.ldapCaCertificate),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
.describe(LDAP_AUTH.ATTACH.accessTokenTrustedIps),
accessTokenTTL: z
.number()
.int()
.min(0)
.max(315360000)
.default(2592000)
.describe(LDAP_AUTH.ATTACH.accessTokenTTL),
accessTokenMaxTTL: z
.number()
.int()
.min(1)
.max(315360000)
.default(2592000)
.describe(LDAP_AUTH.ATTACH.accessTokenMaxTTL),
accessTokenNumUsesLimit: z
.number()
.int()
.min(0)
.default(0)
.describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit)
})
.refine(
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
"Access Token TTL cannot be greater than Access Token Max TTL."
),
// Manual configuration
z
.object({
url: z.string().trim().describe(LDAP_AUTH.ATTACH.url),
bindDN: z.string().trim().describe(LDAP_AUTH.ATTACH.bindDN),
bindPass: z.string().trim().describe(LDAP_AUTH.ATTACH.bindPass),
searchBase: z.string().trim().describe(LDAP_AUTH.ATTACH.searchBase),
searchFilter: z
.string()
.trim()
.min(1)
.default("(uid={{username}})")
.refine(isValidLdapFilter, "Invalid LDAP search filter")
.describe(LDAP_AUTH.ATTACH.searchFilter),
allowedFields: AllowedFieldsSchema.array().optional().describe(LDAP_AUTH.ATTACH.allowedFields),
ldapCaCertificate: z.string().trim().optional().describe(LDAP_AUTH.ATTACH.ldapCaCertificate),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
.describe(LDAP_AUTH.ATTACH.accessTokenTrustedIps),
accessTokenTTL: z
.number()
.int()
.min(0)
.max(315360000)
.default(2592000)
.describe(LDAP_AUTH.ATTACH.accessTokenTTL),
accessTokenMaxTTL: z
.number()
.int()
.min(1)
.max(315360000)
.default(2592000)
.describe(LDAP_AUTH.ATTACH.accessTokenMaxTTL),
accessTokenNumUsesLimit: z
.number()
.int()
.min(0)
.default(0)
.describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit)
})
.refine(
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
"Access Token TTL cannot be greater than Access Token Max TTL."
)
]),
response: {
200: z.object({
identityLdapAuth: IdentityLdapAuthsSchema.omit({
@@ -275,7 +330,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
accessTokenMaxTTL: identityLdapAuth.accessTokenMaxTTL,
accessTokenTTL: identityLdapAuth.accessTokenTTL,
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
allowedFields: req.body.allowedFields
allowedFields: req.body.allowedFields,
templateId: identityLdapAuth.templateId
}
}
});
@@ -309,6 +365,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
bindDN: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.bindDN),
bindPass: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.bindPass),
searchBase: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.searchBase),
templateId: z.string().trim().optional().describe(LDAP_AUTH.UPDATE.templateId),
searchFilter: z
.string()
.trim()
@@ -376,7 +433,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
accessTokenTTL: identityLdapAuth.accessTokenTTL,
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
accessTokenTrustedIps: identityLdapAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
allowedFields: req.body.allowedFields
allowedFields: req.body.allowedFields,
templateId: identityLdapAuth.templateId
}
}
});
@@ -413,7 +471,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
}).extend({
bindDN: z.string(),
bindPass: z.string(),
ldapCaCertificate: z.string().optional()
ldapCaCertificate: z.string().optional(),
templateId: z.string().optional().nullable()
})
})
}
+3
View File
@@ -13,6 +13,7 @@ import { registerCaRouter } from "./certificate-authority-router";
import { CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP } from "./certificate-authority-routers";
import { registerCertRouter } from "./certificate-router";
import { registerCertificateTemplateRouter } from "./certificate-template-router";
import { registerEventRouter } from "./event-router";
import { registerExternalGroupOrgRoleMappingRouter } from "./external-group-org-role-mapping-router";
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
import { registerIdentityAliCloudAuthRouter } from "./identity-alicloud-auth-router";
@@ -183,4 +184,6 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
},
{ prefix: "/reminders" }
);
await server.register(registerEventRouter, { prefix: "/events" });
};
@@ -22,6 +22,7 @@ export const registerSecretReminderRouter = async (server: FastifyZodProvider) =
message: z.string().trim().max(1024).optional(),
repeatDays: z.number().min(1).nullable().optional(),
nextReminderDate: z.string().datetime().nullable().optional(),
fromDate: z.string().datetime().nullable().optional(),
recipients: z.string().array().optional()
})
.refine((data) => {
@@ -45,6 +46,7 @@ export const registerSecretReminderRouter = async (server: FastifyZodProvider) =
message: req.body.message,
repeatDays: req.body.repeatDays,
nextReminderDate: req.body.nextReminderDate,
fromDate: req.body.fromDate,
recipients: req.body.recipients
}
});
@@ -21,6 +21,7 @@ import { registerGitLabSyncRouter } from "./gitlab-sync-router";
import { registerHCVaultSyncRouter } from "./hc-vault-sync-router";
import { registerHerokuSyncRouter } from "./heroku-sync-router";
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
import { registerNetlifySyncRouter } from "./netlify-sync-router";
import { registerRailwaySyncRouter } from "./railway-sync-router";
import { registerRenderSyncRouter } from "./render-sync-router";
import { registerSupabaseSyncRouter } from "./supabase-sync-router";
@@ -61,5 +62,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
[SecretSync.Railway]: registerRailwaySyncRouter,
[SecretSync.Checkly]: registerChecklySyncRouter,
[SecretSync.DigitalOceanAppPlatform]: registerDigitalOceanAppPlatformSyncRouter,
[SecretSync.Netlify]: registerNetlifySyncRouter,
[SecretSync.Bitbucket]: registerBitbucketSyncRouter
};
@@ -0,0 +1,17 @@
import {
CreateNetlifySyncSchema,
NetlifySyncSchema,
UpdateNetlifySyncSchema
} from "@app/services/secret-sync/netlify/netlify-sync-schemas";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerNetlifySyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.Netlify,
server,
responseSchema: NetlifySyncSchema,
createSchema: CreateNetlifySyncSchema,
updateSchema: UpdateNetlifySyncSchema
});
@@ -44,6 +44,7 @@ import { GitLabSyncListItemSchema, GitLabSyncSchema } from "@app/services/secret
import { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault";
import { HerokuSyncListItemSchema, HerokuSyncSchema } from "@app/services/secret-sync/heroku";
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
import { NetlifySyncListItemSchema, NetlifySyncSchema } from "@app/services/secret-sync/netlify";
import { RailwaySyncListItemSchema, RailwaySyncSchema } from "@app/services/secret-sync/railway/railway-sync-schemas";
import { RenderSyncListItemSchema, RenderSyncSchema } from "@app/services/secret-sync/render/render-sync-schemas";
import { SupabaseSyncListItemSchema, SupabaseSyncSchema } from "@app/services/secret-sync/supabase";
@@ -82,6 +83,7 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
RailwaySyncSchema,
ChecklySyncSchema,
DigitalOceanAppPlatformSyncSchema,
NetlifySyncSchema,
BitbucketSyncSchema
]);
@@ -114,6 +116,7 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
RailwaySyncListItemSchema,
ChecklySyncListItemSchema,
SupabaseSyncListItemSchema,
NetlifySyncListItemSchema,
BitbucketSyncListItemSchema
]);
@@ -264,6 +264,48 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
method: "DELETE",
url: "/:organizationId/memberships",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Organizations],
description: "Bulk delete organization user memberships",
security: [
{
bearerAuth: []
}
],
params: z.object({
organizationId: z.string().trim().describe(ORGANIZATIONS.BULK_DELETE_USER_MEMBERSHIPS.organizationId)
}),
body: z.object({
membershipIds: z.string().trim().array().describe(ORGANIZATIONS.BULK_DELETE_USER_MEMBERSHIPS.membershipIds)
}),
response: {
200: z.object({
memberships: OrgMembershipsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
if (req.auth.actor !== ActorType.USER) return;
const memberships = await server.services.org.bulkDeleteOrgMemberships({
userId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId,
membershipIds: req.body.membershipIds,
actorOrgId: req.permission.orgId
});
return { memberships };
}
});
server.route({
// TODO: re-think endpoint structure in future so users only need to pass in membershipId bc organizationId is redundant
method: "GET",