Merge branch 'main' into fix-approval-policy-org-check

This commit is contained in:
x032205
2025-10-26 00:31:43 -04:00
1633 changed files with 72182 additions and 20331 deletions
+10 -6
View File
@@ -15,12 +15,13 @@ import fastify from "fastify";
import { Cluster, Redis } from "ioredis";
import { Knex } from "knex";
import { HsmModule } from "@app/ee/services/hsm/hsm-types";
import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig, IS_PACKAGED, TEnvConfig } from "@app/lib/config/env";
import { CustomLogger } from "@app/lib/logger/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TQueueServiceFactory } from "@app/queue";
import { TKmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { TSmtpService } from "@app/services/smtp/smtp-service";
import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
@@ -42,16 +43,16 @@ type TMain = {
logger?: CustomLogger;
queue: TQueueServiceFactory;
keyStore: TKeyStoreFactory;
hsmModule: HsmModule;
redis: Redis | Cluster;
envConfig: TEnvConfig;
superAdminDAL: TSuperAdminDALFactory;
hsmService: THsmServiceFactory;
kmsRootConfigDAL: TKmsRootConfigDALFactory;
};
// Run the server!
export const main = async ({
db,
hsmModule,
auditLogDb,
smtp,
logger,
@@ -59,7 +60,9 @@ export const main = async ({
keyStore,
redis,
envConfig,
superAdminDAL
superAdminDAL,
hsmService,
kmsRootConfigDAL
}: TMain) => {
const appCfg = getConfig();
@@ -148,9 +151,10 @@ export const main = async ({
db,
auditLogDb,
keyStore,
hsmModule,
hsmService,
envConfig,
superAdminDAL
superAdminDAL,
kmsRootConfigDAL
});
await server.register(registerServeUI, {
+3 -3
View File
@@ -43,6 +43,6 @@ export const GenericResourceNameSchema = z
export const BaseSecretNameSchema = z.string().trim().min(1);
export const SecretNameSchema = BaseSecretNameSchema.refine(
(el) => !el.includes(":"),
"Secret name cannot contain colon."
).refine((el) => !el.includes("/"), "Secret name cannot contain forward slash.");
(el) => !el.includes(":") && !el.includes("/"),
"Secret name cannot contain colon or forward slash."
);
@@ -8,6 +8,7 @@ import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types";
import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto";
import { BadRequestError } from "@app/lib/errors";
import { slugSchema } from "@app/server/lib/schemas";
import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
@@ -20,6 +21,8 @@ export type TAuthMode =
tokenVersionId: string; // the session id of token used
user: TUsers;
orgId: string;
rootOrgId: string;
parentOrgId: string;
authMethod: AuthMethod;
isMfaVerified?: boolean;
token: AuthModeJwtTokenPayload;
@@ -31,6 +34,8 @@ export type TAuthMode =
userId: string;
user: TUsers;
orgId: string;
rootOrgId: string;
parentOrgId: string;
token: string;
}
| {
@@ -39,6 +44,8 @@ export type TAuthMode =
actor: ActorType.SERVICE;
serviceTokenId: string;
orgId: string;
rootOrgId: string;
parentOrgId: string;
authMethod: null;
token: string;
}
@@ -48,6 +55,8 @@ export type TAuthMode =
identityId: string;
identityName: string;
orgId: string;
rootOrgId: string;
parentOrgId: string;
authMethod: null;
isInstanceAdmin?: boolean;
token: TIdentityAccessTokenJwtPayload;
@@ -57,6 +66,8 @@ export type TAuthMode =
actor: ActorType.SCIM_CLIENT;
scimTokenId: string;
orgId: string;
rootOrgId: string;
parentOrgId: string;
authMethod: null;
};
@@ -136,17 +147,26 @@ export const injectIdentity = fp(
if (!authMode) return;
const subOrganizationSelector = req.headers?.["x-infisical-org"] as string | undefined;
if (subOrganizationSelector) {
await slugSchema().parseAsync(subOrganizationSelector);
}
switch (authMode) {
case AuthMode.JWT: {
const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token);
const { user, tokenVersionId, orgId, rootOrgId, parentOrgId } =
await server.services.authToken.fnValidateJwtIdentity(token, subOrganizationSelector);
requestContext.set("orgId", orgId);
req.auth = {
authMode: AuthMode.JWT,
user,
userId: user.id,
tokenVersionId,
actor,
orgId: orgId as string,
orgId,
rootOrgId,
parentOrgId,
authMethod: token.authMethod,
isMfaVerified: token.isMfaVerified,
token
@@ -154,13 +174,19 @@ export const injectIdentity = fp(
break;
}
case AuthMode.IDENTITY_ACCESS_TOKEN: {
const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp);
const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(
token,
subOrganizationSelector,
req.realIp
);
const serverCfg = await getServerCfg();
requestContext.set("orgId", identity.orgId);
req.auth = {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
actor,
orgId: identity.orgId,
rootOrgId: identity.rootOrgId,
parentOrgId: identity.parentOrgId,
identityId: identity.identityId,
identityName: identity.name,
authMethod: null,
@@ -190,8 +216,14 @@ export const injectIdentity = fp(
case AuthMode.SERVICE_TOKEN: {
const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token);
requestContext.set("orgId", serviceToken.orgId);
if (subOrganizationSelector)
throw new BadRequestError({ message: `Service token doesn't support sub organization selector` });
req.auth = {
orgId: serviceToken.orgId,
rootOrgId: serviceToken.rootOrgId,
parentOrgId: serviceToken.parentOrgId,
authMode: AuthMode.SERVICE_TOKEN as const,
serviceToken,
serviceTokenId: serviceToken.id,
@@ -202,22 +234,27 @@ export const injectIdentity = fp(
break;
}
case AuthMode.API_KEY: {
const user = await server.services.apiKey.fnValidateApiKey(token as string);
req.auth = {
authMode: AuthMode.API_KEY as const,
userId: user.id,
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,
token: token as string
};
break;
throw new BadRequestError({
message: "API key authentication is not supported anymore. Please switch to identity authentication."
});
}
case AuthMode.SCIM_TOKEN: {
const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token);
requestContext.set("orgId", orgId);
req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId, authMethod: null };
if (subOrganizationSelector)
throw new BadRequestError({ message: `SCIM token doesn't support sub organization selector` });
req.auth = {
authMode: AuthMode.SCIM_TOKEN,
actor,
scimTokenId,
orgId,
authMethod: null,
// scim cannot be done for sub organization
rootOrgId: orgId,
parentOrgId: orgId
};
break;
}
default:
@@ -14,7 +14,9 @@ export const injectPermission = fp(async (server) => {
type: ActorType.USER,
id: req.auth.userId,
orgId: req.auth.orgId, // if the req.auth.authMode is AuthMode.API_KEY, the orgId will be "API_KEY"
authMethod: req.auth.authMethod // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null
authMethod: req.auth.authMethod, // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null
rootOrgId: req.auth.rootOrgId,
parentOrgId: req.auth.parentOrgId
};
logger.info(
@@ -25,7 +27,9 @@ export const injectPermission = fp(async (server) => {
type: ActorType.IDENTITY,
id: req.auth.identityId,
orgId: req.auth.orgId,
authMethod: null
authMethod: null,
rootOrgId: req.auth.rootOrgId,
parentOrgId: req.auth.parentOrgId
};
logger.info(
@@ -36,6 +40,8 @@ export const injectPermission = fp(async (server) => {
type: ActorType.SERVICE,
id: req.auth.serviceTokenId,
orgId: req.auth.orgId,
rootOrgId: req.auth.rootOrgId,
parentOrgId: req.auth.parentOrgId,
authMethod: null
};
@@ -47,6 +53,8 @@ export const injectPermission = fp(async (server) => {
type: ActorType.SCIM_CLIENT,
id: req.auth.scimTokenId,
orgId: req.auth.orgId,
rootOrgId: req.auth.rootOrgId,
parentOrgId: req.auth.parentOrgId,
authMethod: null
};
File diff suppressed because it is too large Load Diff
@@ -209,11 +209,11 @@ export const SanitizedIdentityPrivilegeSchema = IdentityProjectAdditionalPrivile
)
});
export const SanitizedRoleSchema = ProjectRolesSchema.extend({
export const SanitizedRoleSchema = ProjectRolesSchema.omit({ version: true }).extend({
permissions: UnpackedPermissionSchema.array()
});
export const SanitizedRoleSchemaV1 = ProjectRolesSchema.extend({
export const SanitizedRoleSchemaV1 = ProjectRolesSchema.omit({ version: true }).extend({
permissions: UnpackedPermissionSchema.array().transform((caslPermission) =>
// first map and remove other actions of folder permission
caslPermission
+30 -18
View File
@@ -5,6 +5,7 @@ import {
IdentitiesSchema,
OrganizationsSchema,
OrgMembershipsSchema,
OrgMembershipStatus,
SuperAdminSchema,
UsersSchema
} from "@app/db/schemas";
@@ -172,7 +173,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
email: true,
id: true,
superAdmin: true
}).array()
}).array(),
total: z.number()
})
}
},
@@ -182,13 +184,11 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
});
},
handler: async (req) => {
const users = await server.services.superAdmin.getUsers({
const result = await server.services.superAdmin.getUsers({
...req.query
});
return {
users
};
return result;
}
});
@@ -230,7 +230,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
createdAt: z.date()
})
.array()
}).array()
}).array(),
total: z.number()
})
}
},
@@ -240,13 +241,11 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
});
},
handler: async (req) => {
const organizations = await server.services.superAdmin.getOrganizations({
const result = await server.services.superAdmin.getOrganizations({
...req.query
});
return {
organizations
};
return result;
}
});
@@ -281,7 +280,10 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
);
return {
organizationMembership
organizationMembership: {
...organizationMembership,
status: organizationMembership?.status || OrgMembershipStatus.Accepted
}
};
}
});
@@ -337,7 +339,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
.extend({
isInstanceAdmin: z.boolean()
})
.array()
.array(),
total: z.number()
})
}
},
@@ -347,13 +350,11 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
});
},
handler: async (req) => {
const identities = await server.services.superAdmin.getIdentities({
const result = await server.services.superAdmin.getIdentities({
...req.query
});
return {
identities
};
return result;
}
});
@@ -895,7 +896,13 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
},
handler: async (req) => {
const organizationMembership = await server.services.superAdmin.resendOrgInvite(req.params, req.permission);
return { organizationMembership };
return {
organizationMembership: {
...organizationMembership,
status: organizationMembership?.status || OrgMembershipStatus.Accepted
}
};
}
});
@@ -925,7 +932,12 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
req.params.organizationId,
req.permission
);
return { organizationMembership };
return {
organizationMembership: {
...organizationMembership,
status: organizationMembership?.status || OrgMembershipStatus.Accepted
}
};
}
});
@@ -77,6 +77,10 @@ import {
HumanitecConnectionListItemSchema,
SanitizedHumanitecConnectionSchema
} from "@app/services/app-connection/humanitec";
import {
LaravelForgeConnectionListItemSchema,
SanitizedLaravelForgeConnectionSchema
} from "@app/services/app-connection/laravel-forge";
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";
@@ -158,7 +162,8 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedNetlifyConnectionSchema.options,
...SanitizedOktaConnectionSchema.options,
...SanitizedAzureADCSConnectionSchema.options,
...SanitizedRedisConnectionSchema.options
...SanitizedRedisConnectionSchema.options,
...SanitizedLaravelForgeConnectionSchema.options
]);
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
@@ -200,7 +205,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
NetlifyConnectionListItemSchema,
OktaConnectionListItemSchema,
AzureADCSConnectionListItemSchema,
RedisConnectionListItemSchema
RedisConnectionListItemSchema,
LaravelForgeConnectionListItemSchema
]);
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
@@ -24,6 +24,7 @@ import { registerGitLabConnectionRouter } from "./gitlab-connection-router";
import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router";
import { registerHerokuConnectionRouter } from "./heroku-connection-router";
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
import { registerLaravelForgeConnectionRouter } from "./laravel-forge-connection-router";
import { registerLdapConnectionRouter } from "./ldap-connection-router";
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
import { registerMySqlConnectionRouter } from "./mysql-connection-router";
@@ -71,6 +72,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.OnePass]: registerOnePassConnectionRouter,
[AppConnection.Heroku]: registerHerokuConnectionRouter,
[AppConnection.Render]: registerRenderConnectionRouter,
[AppConnection.LaravelForge]: registerLaravelForgeConnectionRouter,
[AppConnection.Flyio]: registerFlyioConnectionRouter,
[AppConnection.GitLab]: registerGitLabConnectionRouter,
[AppConnection.Cloudflare]: registerCloudflareConnectionRouter,
@@ -0,0 +1,128 @@
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 {
CreateLaravelForgeConnectionSchema,
SanitizedLaravelForgeConnectionSchema,
UpdateLaravelForgeConnectionSchema
} from "@app/services/app-connection/laravel-forge";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerLaravelForgeConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.LaravelForge,
server,
sanitizedResponseSchema: SanitizedLaravelForgeConnectionSchema,
createSchema: CreateLaravelForgeConnectionSchema,
updateSchema: UpdateLaravelForgeConnectionSchema
});
server.route({
method: "GET",
url: `/:connectionId/organizations`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z
.object({
id: z.string(),
name: z.string(),
slug: z.string()
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const organizations = await server.services.appConnection.laravelForge.listOrganizations(
connectionId,
req.permission
);
return organizations;
}
});
server.route({
method: "GET",
url: `/:connectionId/servers`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
querystring: z.object({
organizationSlug: z.string()
}),
response: {
200: z
.object({
id: z.string(),
name: z.string()
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const { organizationSlug } = req.query;
const servers = await server.services.appConnection.laravelForge.listServers(
connectionId,
req.permission,
organizationSlug
);
return servers;
}
});
server.route({
method: "GET",
url: `/:connectionId/sites`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
querystring: z.object({
organizationSlug: z.string(),
serverId: z.string()
}),
response: {
200: z
.object({
id: z.string(),
name: z.string()
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const { organizationSlug, serverId } = req.query;
const sites = await server.services.appConnection.laravelForge.listSites(
connectionId,
req.permission,
organizationSlug,
serverId
);
return sites;
}
});
};
@@ -94,6 +94,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
decodedToken.userId,
decodedToken.organizationId,
decodedToken.authMethod,
decodedToken.organizationId,
decodedToken.organizationId
);
if (org && org.userTokenExpiration) {
@@ -0,0 +1,506 @@
import RE2 from "re2";
import { z } from "zod";
import { PkiCertificateProfilesSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CertStatus } from "@app/services/certificate/certificate-types";
import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types";
export const registerCertificateProfilesRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
body: z
.object({
projectId: z.string().min(1),
caId: z.string().uuid(),
certificateTemplateId: z.string().uuid(),
slug: z
.string()
.min(1)
.max(255)
.regex(new RE2("^[a-z0-9-]+$"), "Slug must contain only lowercase letters, numbers, and hyphens"),
description: z.string().max(1000).optional(),
enrollmentType: z.nativeEnum(EnrollmentType),
estConfig: z
.object({
disableBootstrapCaValidation: z.boolean().default(false),
passphrase: z.string().min(1),
caChain: z.string().optional()
})
.optional(),
apiConfig: z
.object({
autoRenew: z.boolean().default(false),
autoRenewDays: z.number().min(1).max(365).optional()
})
.optional()
})
.refine(
(data) => {
if (data.enrollmentType === EnrollmentType.EST) {
if (!data.estConfig) {
return false;
}
if (data.apiConfig) {
return false;
}
}
if (data.enrollmentType === EnrollmentType.API) {
if (!data.apiConfig) {
return false;
}
if (data.estConfig) {
return false;
}
}
return true;
},
{
message:
"EST enrollment type requires EST configuration and cannot have API configuration. API enrollment type requires API configuration and cannot have EST configuration."
}
),
response: {
200: z.object({
certificateProfile: PkiCertificateProfilesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateProfile = await server.services.certificateProfile.createProfile({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.body.projectId,
data: req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.projectId,
event: {
type: EventType.CREATE_CERTIFICATE_PROFILE,
metadata: {
certificateProfileId: certificateProfile.id,
name: certificateProfile.slug,
projectId: certificateProfile.projectId,
enrollmentType: certificateProfile.enrollmentType
}
}
});
return { certificateProfile };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
querystring: z.object({
projectId: z.string().min(1),
offset: z.coerce.number().min(0).default(0),
limit: z.coerce.number().min(1).max(100).default(20),
search: z.string().optional(),
enrollmentType: z.nativeEnum(EnrollmentType).optional(),
caId: z.string().uuid().optional(),
includeMetrics: z.coerce.boolean().optional().default(false),
expiringDays: z.coerce.number().min(1).max(365).optional().default(7)
}),
response: {
200: z.object({
certificateProfiles: PkiCertificateProfilesSchema.extend({
metrics: z
.object({
profileId: z.string(),
totalCertificates: z.number(),
activeCertificates: z.number(),
expiredCertificates: z.number(),
expiringCertificates: z.number(),
revokedCertificates: z.number()
})
.optional(),
estConfig: z
.object({
id: z.string(),
disableBootstrapCaValidation: z.boolean(),
passphrase: z.string().optional(),
caChain: z.string().optional()
})
.optional(),
apiConfig: z
.object({
id: z.string(),
autoRenew: z.boolean(),
autoRenewDays: z.number().optional()
})
.optional()
}).array(),
totalCount: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { profiles, totalCount } = await server.services.certificateProfile.listProfiles({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.query.projectId,
event: {
type: EventType.LIST_CERTIFICATE_PROFILES,
metadata: {
projectId: req.query.projectId
}
}
});
return { certificateProfiles: profiles, totalCount };
}
});
server.route({
method: "GET",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
params: z.object({
id: z.string().uuid()
}),
querystring: z.object({
includeMetrics: z.coerce.boolean().optional().default(false),
expiringDays: z.coerce.number().min(1).max(365).optional().default(7)
}),
response: {
200: z.object({
certificateProfile: PkiCertificateProfilesSchema.extend({
certificateAuthority: z
.object({
id: z.string(),
projectId: z.string(),
status: z.string(),
name: z.string()
})
.optional(),
certificateTemplate: z
.object({
id: z.string(),
projectId: z.string(),
name: z.string(),
description: z.string().optional()
})
.optional(),
estConfig: z
.object({
id: z.string(),
disableBootstrapCaValidation: z.boolean(),
passphrase: z.string(),
caChain: z.string().optional()
})
.optional(),
apiConfig: z
.object({
id: z.string(),
autoRenew: z.boolean(),
autoRenewDays: z.number().optional()
})
.optional(),
metrics: z
.object({
profileId: z.string(),
totalCertificates: z.number(),
activeCertificates: z.number(),
expiredCertificates: z.number(),
expiringCertificates: z.number(),
revokedCertificates: z.number()
})
.optional()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateProfile = await server.services.certificateProfile.getProfileByIdWithConfigs({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.params.id
});
let result = certificateProfile;
if (req.query.includeMetrics) {
const metrics = await server.services.certificateProfile.getProfileMetrics({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.params.id,
expiringDays: req.query.expiringDays
});
result = { ...certificateProfile, metrics };
}
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateProfile.projectId,
event: {
type: EventType.GET_CERTIFICATE_PROFILE,
metadata: {
certificateProfileId: certificateProfile.id,
name: certificateProfile.slug
}
}
});
return { certificateProfile: result };
}
});
server.route({
method: "GET",
url: "/slug/:slug",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
params: z.object({
slug: z.string().min(1)
}),
querystring: z.object({
projectId: z.string().min(1)
}),
response: {
200: z.object({
certificateProfile: PkiCertificateProfilesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateProfile = await server.services.certificateProfile.getProfileBySlug({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.query.projectId,
slug: req.params.slug
});
return { certificateProfile };
}
});
server.route({
method: "PATCH",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
params: z.object({
id: z.string().uuid()
}),
body: z
.object({
slug: z
.string()
.min(1)
.max(255)
.regex(new RE2("^[a-z0-9-]+$"), "Slug must contain only lowercase letters, numbers, and hyphens")
.optional(),
description: z.string().max(1000).optional(),
enrollmentType: z.nativeEnum(EnrollmentType).optional(),
estConfig: z
.object({
disableBootstrapCaValidation: z.boolean().default(false),
passphrase: z.string().min(1).optional(),
caChain: z.string().optional()
})
.optional(),
apiConfig: z
.object({
autoRenew: z.boolean().default(false),
autoRenewDays: z.number().min(1).max(365).optional()
})
.optional()
})
.refine(
(data) => {
if (data.enrollmentType === EnrollmentType.EST) {
if (data.apiConfig) {
return false;
}
}
if (data.enrollmentType === EnrollmentType.API) {
if (data.estConfig) {
return false;
}
}
return true;
},
{
message: "Cannot have EST config with API enrollment type or API config with EST enrollment type."
}
),
response: {
200: z.object({
certificateProfile: PkiCertificateProfilesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateProfile = await server.services.certificateProfile.updateProfile({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.params.id,
data: req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateProfile.projectId,
event: {
type: EventType.UPDATE_CERTIFICATE_PROFILE,
metadata: {
certificateProfileId: certificateProfile.id,
name: certificateProfile.slug
}
}
});
return { certificateProfile };
}
});
server.route({
method: "DELETE",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
params: z.object({
id: z.string().uuid()
}),
response: {
200: z.object({
certificateProfile: PkiCertificateProfilesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateProfile = await server.services.certificateProfile.deleteProfile({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.params.id
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateProfile.projectId,
event: {
type: EventType.DELETE_CERTIFICATE_PROFILE,
metadata: {
certificateProfileId: certificateProfile.id,
name: certificateProfile.slug
}
}
});
return { certificateProfile };
}
});
server.route({
method: "GET",
url: "/:id/certificates",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
params: z.object({
id: z.string().uuid()
}),
querystring: z.object({
offset: z.coerce.number().min(0).default(0),
limit: z.coerce.number().min(1).max(100).default(20),
status: z.nativeEnum(CertStatus).optional(),
search: z.string().optional()
}),
response: {
200: z.object({
certificates: z.array(
z.object({
id: z.string(),
serialNumber: z.string(),
cn: z.string(),
status: z.string(),
notBefore: z.date(),
notAfter: z.date(),
revokedAt: z.date().nullable().optional(),
createdAt: z.date()
})
)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificates = await server.services.certificateProfile.getProfileCertificates({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.params.id,
...req.query
});
return { certificates };
}
});
};
@@ -52,7 +52,8 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
event: {
type: EventType.GET_CERTIFICATE_TEMPLATE,
metadata: {
certificateTemplateId: certificateTemplate.id
certificateTemplateId: certificateTemplate.id,
name: certificateTemplate.name
}
}
});
@@ -121,7 +122,8 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
name: certificateTemplate.name,
commonName: certificateTemplate.commonName,
subjectAlternativeName: certificateTemplate.subjectAlternativeName,
ttl: certificateTemplate.ttl
ttl: certificateTemplate.ttl,
projectId: certificateTemplate.projectId
}
}
});
@@ -184,9 +186,9 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
type: EventType.UPDATE_CERTIFICATE_TEMPLATE,
metadata: {
certificateTemplateId: certificateTemplate.id,
name: certificateTemplate.name,
caId: certificateTemplate.caId,
pkiCollectionId: certificateTemplate.pkiCollectionId as string,
name: certificateTemplate.name,
commonName: certificateTemplate.commonName,
subjectAlternativeName: certificateTemplate.subjectAlternativeName,
ttl: certificateTemplate.ttl
@@ -230,7 +232,8 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
event: {
type: EventType.DELETE_CERTIFICATE_TEMPLATE,
metadata: {
certificateTemplateId: certificateTemplate.id
certificateTemplateId: certificateTemplate.id,
name: certificateTemplate.name
}
}
});
@@ -1,9 +1,13 @@
import { z } from "zod";
import {
AccessScope,
OrgMembershipRole,
OrgMembershipsSchema,
OrgMembershipStatus,
ProjectMembershipsSchema,
ProjectUserMembershipRolesSchema,
TemporaryPermissionMode,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
@@ -13,7 +17,6 @@ import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -66,14 +69,23 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const memberships = await server.services.projectMembership.getProjectMemberships({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId
const { data: memberships } = await server.services.membershipUser.listMemberships({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.workspaceId
},
data: {}
});
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
userId: el.actorUserId as string,
projectId: req.params.workspaceId
}))
};
}
});
@@ -124,15 +136,30 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.getProjectMembershipById({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
id: req.params.membershipId
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const membership = await server.services.membershipUser.getMembershipByUserId({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.workspaceId
},
selector: {
userId
}
});
return { membership };
return {
membership: {
...membership,
userId,
projectId: req.params.workspaceId
}
};
}
});
@@ -241,14 +268,22 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_PROJECT_MEMBER,
metadata: data.map(({ userId }) => ({
userId: userId || "",
metadata: data.map(({ actorUserId }) => ({
userId: actorUserId || "",
email: ""
}))
}
});
return { data, success: true };
return {
data: data.map((el) => ({
...el,
orgId: req.permission.orgId,
role: OrgMembershipRole.Member,
status: el.status || OrgMembershipStatus.Accepted
})),
success: true
};
}
});
@@ -282,7 +317,7 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
@@ -300,30 +335,28 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const roles = await server.services.projectMembership.updateProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
membershipId: req.params.membershipId,
roles: req.body.roles
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const { membership } = await server.services.membershipUser.updateMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.workspaceId
},
selector: {
userId
},
data: {
roles: req.body.roles
}
});
// await server.services.auditLog.createAuditLog({
// ...req.auditLogInfo,
// projectId: req.params.workspaceId,
// event: {
// type: EventType.UPDATE_USER_WORKSPACE_ROLE,
// metadata: {
// userId: membership.userId,
// newRole: req.body.role,
// oldRole: membership.role,
// email: ""
// }
// }
// });
return { roles };
return { roles: membership.roles.map((el) => ({ ...el, projectMembershipId: req.params.membershipId })) };
}
});
@@ -352,13 +385,22 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.deleteProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
membershipId: req.params.membershipId
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const { membership } = await server.services.membershipUser.deleteMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.workspaceId
},
selector: {
userId
}
});
await server.services.auditLog.createAuditLog({
@@ -367,12 +409,19 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
event: {
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
userId: membership.actorUserId as string,
email: ""
}
}
});
return { membership };
return {
membership: {
...membership,
userId,
projectId: req.params.workspaceId
}
};
}
});
};
@@ -318,7 +318,8 @@ export const registerDeprecatedSecretFolderRouter = async (server: FastifyZodPro
...req.body,
projectId: req.body.workspaceId,
idOrName: req.params.folderIdOrName,
path
path,
forceDelete: true
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
@@ -1,19 +1,21 @@
import { z } from "zod";
import {
AccessScope,
GroupProjectMembershipsSchema,
GroupsSchema,
ProjectMembershipRole,
ProjectUserMembershipRolesSchema,
TemporaryPermissionMode,
UsersSchema
} from "@app/db/schemas";
import { EFilterReturnedUsers } from "@app/ee/services/group/group-types";
import { ApiDocsTags, GROUPS, PROJECTS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { isUuidV4 } from "@app/lib/validator";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerGroupProjectRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -54,7 +56,7 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
@@ -73,17 +75,32 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.addGroupToProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
roles: req.body.roles || [{ role: req.body.role }],
projectId: req.params.projectId,
groupIdOrName: req.params.groupIdOrName
let groupId = req.params.groupIdOrName;
if (!isUuidV4(req.params.groupIdOrName)) {
const groupDetails = await server.services.convertor.getGroupIdFromName(groupId, req.permission.orgId);
groupId = groupDetails.groupId;
}
const { membership: groupMembership } = await server.services.membershipGroup.createMembership({
permission: req.permission,
data: {
groupId,
roles: req.body.roles || [{ role: req.body.role, isTemporary: false }]
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMembership };
return {
groupMembership: {
...groupMembership,
projectId: req.params.projectId,
groupId: groupMembership.actorGroupId as string
}
};
}
});
@@ -115,7 +132,7 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
@@ -131,17 +148,22 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const roles = await server.services.groupProject.updateGroupInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
groupId: req.params.groupId,
roles: req.body.roles
const { membership: groupMembership } = await server.services.membershipGroup.updateMembership({
permission: req.permission,
selector: {
groupId: req.params.groupId
},
data: {
roles: req.body.roles
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { roles };
return { roles: groupMembership.roles.map((el) => ({ ...el, projectMembershipId: groupMembership.id })) };
}
});
@@ -172,16 +194,25 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.removeGroupFromProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
groupId: req.params.groupId,
projectId: req.params.projectId
const { membership: groupMembership } = await server.services.membershipGroup.deleteMembership({
permission: req.permission,
selector: {
groupId: req.params.groupId
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMembership };
return {
groupMembership: {
...groupMembership,
projectId: req.params.projectId,
groupId: groupMembership.actorGroupId as string
}
};
}
});
@@ -233,15 +264,17 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const groupMemberships = await server.services.groupProject.listGroupsInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId
const { memberships: groupMemberships } = await server.services.membershipGroup.listMemberships({
permission: req.permission,
data: {},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMemberships };
return { groupMemberships: groupMemberships.map((el) => ({ ...el, groupId: el.actorGroupId as string })) };
}
});
@@ -292,15 +325,25 @@ export const registerGroupProjectRouter = async (server: FastifyZodProvider) =>
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.getGroupInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.params
const { membership: groupMembership } = await server.services.membershipGroup.getMembershipByGroupId({
permission: req.permission,
selector: {
groupId: req.params.groupId
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMembership };
return {
groupMembership: {
...groupMembership,
projectId: req.params.projectId,
groupId: groupMembership.actorGroupId as string
}
};
}
});
@@ -13,7 +13,7 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Renew access token",
description: "Renew machine identity access token",
body: z.object({
accessToken: z.string().trim().describe(UNIVERSAL_AUTH.RENEW_ACCESS_TOKEN.accessToken)
}),
@@ -48,7 +48,7 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Revoke access token",
description: "Revoke machine identity access token",
body: z.object({
accessToken: z.string().trim().describe(UNIVERSAL_AUTH.REVOKE_ACCESS_TOKEN.accessToken)
}),
@@ -21,7 +21,7 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
schema: {
hide: false,
tags: [ApiDocsTags.AliCloudAuth],
description: "Login with Alibaba Cloud Auth",
description: "Login with Alibaba Cloud Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(ALICLOUD_AUTH.LOGIN.identityId),
Action: z.enum(["GetCallerIdentity"]).describe(ALICLOUD_AUTH.LOGIN.Action),
@@ -73,12 +73,12 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
}
},
handler: async (req) => {
const { identityAliCloudAuth, accessToken, identityAccessToken, identityMembershipOrg } =
const { identityAliCloudAuth, accessToken, identityAccessToken, identity } =
await server.services.identityAliCloudAuth.login(req.body);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identity.orgId,
event: {
type: EventType.LOGIN_IDENTITY_ALICLOUD_AUTH,
metadata: {
@@ -108,7 +108,7 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
schema: {
hide: false,
tags: [ApiDocsTags.AliCloudAuth],
description: "Attach Alibaba Cloud Auth configuration onto identity",
description: "Attach Alibaba Cloud Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -200,7 +200,7 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
schema: {
hide: false,
tags: [ApiDocsTags.AliCloudAuth],
description: "Update Alibaba Cloud Auth configuration on identity",
description: "Update Alibaba Cloud Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -292,7 +292,7 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
schema: {
hide: false,
tags: [ApiDocsTags.AliCloudAuth],
description: "Retrieve Alibaba Cloud Auth configuration on identity",
description: "Retrieve Alibaba Cloud Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -340,7 +340,7 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
schema: {
hide: false,
tags: [ApiDocsTags.AliCloudAuth],
description: "Delete Alibaba Cloud Auth configuration on identity",
description: "Delete Alibaba Cloud Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -23,7 +23,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.AwsAuth],
description: "Login with AWS Auth",
description: "Login with AWS Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(AWS_AUTH.LOGIN.identityId),
iamHttpRequestMethod: z.string().default("POST").describe(AWS_AUTH.LOGIN.iamHttpRequestMethod),
@@ -40,12 +40,12 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
}
},
handler: async (req) => {
const { identityAwsAuth, accessToken, identityAccessToken, identityMembershipOrg } =
const { identityAwsAuth, accessToken, identityAccessToken, identity } =
await server.services.identityAwsAuth.login(req.body);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identity.orgId,
event: {
type: EventType.LOGIN_IDENTITY_AWS_AUTH,
metadata: {
@@ -75,7 +75,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.AwsAuth],
description: "Attach AWS Auth configuration onto identity",
description: "Attach AWS Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -171,7 +171,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.AwsAuth],
description: "Update AWS Auth configuration on identity",
description: "Update AWS Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -255,7 +255,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.AwsAuth],
description: "Retrieve AWS Auth configuration on identity",
description: "Retrieve AWS Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -303,7 +303,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.AwsAuth],
description: "Delete AWS Auth configuration on identity",
description: "Delete AWS Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -20,7 +20,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.AzureAuth],
description: "Login with Azure Auth",
description: "Login with Azure Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(AZURE_AUTH.LOGIN.identityId),
jwt: z.string()
@@ -35,12 +35,12 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
}
},
handler: async (req) => {
const { identityAzureAuth, accessToken, identityAccessToken, identityMembershipOrg } =
const { identityAzureAuth, accessToken, identityAccessToken, identity } =
await server.services.identityAzureAuth.login(req.body);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg.orgId,
orgId: identity.orgId,
event: {
type: EventType.LOGIN_IDENTITY_AZURE_AUTH,
metadata: {
@@ -70,7 +70,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.AzureAuth],
description: "Attach Azure Auth configuration onto identity",
description: "Attach Azure Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -165,7 +165,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.AzureAuth],
description: "Update Azure Auth configuration on identity",
description: "Update Azure Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -255,7 +255,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.AzureAuth],
description: "Retrieve Azure Auth configuration on identity",
description: "Retrieve Azure Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -304,7 +304,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.AzureAuth],
description: "Delete Azure Auth configuration on identity",
description: "Delete Azure Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -20,7 +20,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.GcpAuth],
description: "Login with GCP Auth",
description: "Login with GCP Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(GCP_AUTH.LOGIN.identityId),
jwt: z.string()
@@ -35,12 +35,12 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
}
},
handler: async (req) => {
const { identityGcpAuth, accessToken, identityAccessToken, identityMembershipOrg } =
const { identityGcpAuth, accessToken, identityAccessToken, identity } =
await server.services.identityGcpAuth.login(req.body);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identity.orgId,
event: {
type: EventType.LOGIN_IDENTITY_GCP_AUTH,
metadata: {
@@ -70,7 +70,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.GcpAuth],
description: "Attach GCP Auth configuration onto identity",
description: "Attach GCP Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -163,7 +163,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.GcpAuth],
description: "Update GCP Auth configuration on identity",
description: "Update GCP Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -249,7 +249,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.GcpAuth],
description: "Retrieve GCP Auth configuration on identity",
description: "Retrieve GCP Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -298,7 +298,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.GcpAuth],
description: "Delete GCP Auth configuration on identity",
description: "Delete GCP Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -96,7 +96,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.JwtAuth],
description: "Login with JWT Auth",
description: "Login with JWT Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(JWT_AUTH.LOGIN.identityId),
jwt: z.string().trim()
@@ -111,7 +111,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
}
},
handler: async (req) => {
const { identityJwtAuth, accessToken, identityAccessToken, identityMembershipOrg } =
const { identityJwtAuth, accessToken, identityAccessToken, identity } =
await server.services.identityJwtAuth.login({
identityId: req.body.identityId,
jwt: req.body.jwt
@@ -119,7 +119,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identity.orgId,
event: {
type: EventType.LOGIN_IDENTITY_JWT_AUTH,
metadata: {
@@ -148,7 +148,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.JwtAuth],
description: "Attach JWT Auth configuration onto identity",
description: "Attach JWT Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -217,7 +217,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.JwtAuth],
description: "Update JWT Auth configuration on identity",
description: "Update JWT Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -283,7 +283,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.JwtAuth],
description: "Retrieve JWT Auth configuration on identity",
description: "Retrieve JWT Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -332,7 +332,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.JwtAuth],
description: "Delete JWT Auth configuration on identity",
description: "Delete JWT Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -41,7 +41,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
schema: {
hide: false,
tags: [ApiDocsTags.KubernetesAuth],
description: "Login with Kubernetes Auth",
description: "Login with Kubernetes Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(KUBERNETES_AUTH.LOGIN.identityId),
jwt: z.string().trim()
@@ -56,7 +56,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
}
},
handler: async (req) => {
const { identityKubernetesAuth, accessToken, identityAccessToken, identityMembershipOrg } =
const { identityKubernetesAuth, accessToken, identityAccessToken, identity } =
await server.services.identityKubernetesAuth.login({
identityId: req.body.identityId,
jwt: req.body.jwt
@@ -64,7 +64,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identity.orgId,
event: {
type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH,
metadata: {
@@ -93,7 +93,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
schema: {
hide: false,
tags: [ApiDocsTags.KubernetesAuth],
description: "Attach Kubernetes Auth configuration onto identity",
description: "Attach Kubernetes Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -240,7 +240,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
schema: {
hide: false,
tags: [ApiDocsTags.KubernetesAuth],
description: "Update Kubernetes Auth configuration on identity",
description: "Update Kubernetes Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -383,7 +383,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
schema: {
hide: false,
tags: [ApiDocsTags.KubernetesAuth],
description: "Retrieve Kubernetes Auth configuration on identity",
description: "Retrieve Kubernetes Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -432,7 +432,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
schema: {
hide: false,
tags: [ApiDocsTags.KubernetesAuth],
description: "Delete Kubernetes Auth configuration on identity",
description: "Delete Kubernetes Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -120,7 +120,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Login with LDAP Auth",
description: "Login with LDAP Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(LDAP_AUTH.LOGIN.identityId),
username: z.string().describe(LDAP_AUTH.LOGIN.username),
@@ -162,13 +162,13 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
const { identityId, user } = req.passportMachineIdentity;
const { accessToken, identityLdapAuth, identityMembershipOrg } = await server.services.identityLdapAuth.login({
const { accessToken, identityLdapAuth, identity } = await server.services.identityLdapAuth.login({
identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identity.orgId,
event: {
type: EventType.LOGIN_IDENTITY_LDAP_AUTH,
metadata: {
@@ -198,7 +198,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Attach LDAP Auth configuration onto identity",
description: "Attach LDAP Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -389,7 +389,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Update LDAP Auth configuration on identity",
description: "Update LDAP Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -510,7 +510,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Retrieve LDAP Auth configuration on identity",
description: "Retrieve LDAP Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -568,7 +568,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Delete LDAP Auth configuration on identity",
description: "Delete LDAP Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -621,7 +621,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Clear LDAP Auth Lockouts for identity",
description: "Clear LDAP Auth Lockouts for machine identity",
security: [
{
bearerAuth: []
@@ -20,7 +20,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OciAuth],
description: "Login with OCI Auth",
description: "Login with OCI Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(OCI_AUTH.LOGIN.identityId),
userOcid: z.string().trim().describe(OCI_AUTH.LOGIN.userOcid),
@@ -52,12 +52,12 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
}
},
handler: async (req) => {
const { identityOciAuth, accessToken, identityAccessToken, identityMembershipOrg } =
const { identityOciAuth, accessToken, identityAccessToken, identity } =
await server.services.identityOciAuth.login(req.body);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identity.orgId,
event: {
type: EventType.LOGIN_IDENTITY_OCI_AUTH,
metadata: {
@@ -87,7 +87,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OciAuth],
description: "Attach OCI Auth configuration onto identity",
description: "Attach OCI Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -176,7 +176,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OciAuth],
description: "Update OCI Auth configuration on identity",
description: "Update OCI Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -259,7 +259,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OciAuth],
description: "Retrieve OCI Auth configuration on identity",
description: "Retrieve OCI Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -307,7 +307,7 @@ export const registerIdentityOciAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OciAuth],
description: "Delete OCI Auth configuration on identity",
description: "Delete OCI Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -44,7 +44,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OidcAuth],
description: "Login with OIDC Auth",
description: "Login with OIDC Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(OIDC_AUTH.LOGIN.identityId),
jwt: z.string().trim()
@@ -59,7 +59,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
}
},
handler: async (req) => {
const { identityOidcAuth, accessToken, identityAccessToken, identityMembershipOrg, oidcTokenData } =
const { identityOidcAuth, accessToken, identityAccessToken, identity, oidcTokenData } =
await server.services.identityOidcAuth.login({
identityId: req.body.identityId,
jwt: req.body.jwt
@@ -67,7 +67,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identity.orgId,
event: {
type: EventType.LOGIN_IDENTITY_OIDC_AUTH,
metadata: {
@@ -100,7 +100,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OidcAuth],
description: "Attach OIDC Auth configuration onto identity",
description: "Attach OIDC Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -201,7 +201,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OidcAuth],
description: "Update OIDC Auth configuration on identity",
description: "Update OIDC Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -300,7 +300,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OidcAuth],
description: "Retrieve OIDC Auth configuration on identity",
description: "Retrieve OIDC Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -349,7 +349,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
schema: {
hide: false,
tags: [ApiDocsTags.OidcAuth],
description: "Delete OIDC Auth configuration on identity",
description: "Delete OIDC Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -0,0 +1,137 @@
import { z } from "zod";
import { AccessScope, TemporaryPermissionMode } from "@app/db/schemas";
import { ApiDocsTags, PROJECT_IDENTITIES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
const sanitizedOrgIdentityMembershipSchema = z.object({
id: z.string().uuid(),
orgId: z.string(),
identityId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export const registerOrgIdentityMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/identity-memberships/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
hide: true,
// this is hidden so not updating tags
tags: [ApiDocsTags.ProjectIdentities],
description: "Create org identity membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim()
}),
body: z.object({
roles: z
.array(
z.union([
z.object({
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z
.literal(false)
.default(false)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role)
}),
z.object({
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryMode: z
.nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryRange: z
.string()
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryAccessStartTime: z
.string()
.datetime()
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role)
})
])
)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.description)
.max(1)
}),
response: {
200: z.object({
identityMembership: sanitizedOrgIdentityMembershipSchema
})
}
},
handler: async (req) => {
const { membership } = await server.services.membershipIdentity.createMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Organization,
orgId: req.permission.orgId
},
data: {
identityId: req.params.identityId,
roles: req.body.roles
}
});
return {
identityMembership: { ...membership, identityId: req.params.identityId, orgId: req.permission.orgId }
};
}
});
server.route({
method: "DELETE",
url: "/identity-memberships/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
hide: true,
tags: [ApiDocsTags.ProjectIdentities],
description: "Delete org identity memberships",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.identityId)
}),
response: {
200: z.object({
identityMembership: sanitizedOrgIdentityMembershipSchema
})
}
},
handler: async (req) => {
const { membership } = await server.services.membershipIdentity.deleteMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Organization,
orgId: req.permission.orgId
},
selector: {
identityId: req.params.identityId
}
});
return {
identityMembership: { ...membership, identityId: req.params.identityId, orgId: req.permission.orgId }
};
}
});
};
@@ -1,10 +1,12 @@
import { z } from "zod";
import {
AccessScope,
IdentitiesSchema,
IdentityProjectMembershipsSchema,
ProjectMembershipRole,
ProjectUserMembershipRolesSchema
ProjectUserMembershipRolesSchema,
TemporaryPermissionMode
} from "@app/db/schemas";
import { ApiDocsTags, ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors";
@@ -14,7 +16,6 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectIdentityOrderBy } from "@app/services/identity-project/identity-project-types";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
import { SanitizedProjectSchema } from "../sanitizedSchemas";
@@ -56,7 +57,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryMode: z
.nativeEnum(ProjectUserMembershipTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryRange: z
.string()
@@ -82,16 +83,22 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
const { role, roles } = req.body;
if (!role && !roles) throw new BadRequestError({ message: "You must provide either role or roles field" });
const identityMembership = await server.services.identityProject.createProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId,
roles: roles || [{ role }]
const { membership } = await server.services.membershipIdentity.createMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
data: {
identityId: req.params.identityId,
roles: roles || [{ role, isTemporary: false }]
}
});
return { identityMembership };
return {
identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId }
};
}
});
@@ -130,7 +137,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary),
temporaryMode: z
.nativeEnum(ProjectUserMembershipTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode),
temporaryRange: z
.string()
@@ -153,16 +160,24 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
}
},
handler: async (req) => {
const roles = await server.services.identityProject.updateProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId,
roles: req.body.roles
const { membership } = await server.services.membershipIdentity.updateMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
identityId: req.params.identityId
},
data: {
roles: req.body.roles
}
});
return { roles };
return {
roles: membership.roles.map((el) => ({ ...el, projectMembershipId: membership.id }))
};
}
});
@@ -193,15 +208,21 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.deleteProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId
const { membership } = await server.services.membershipIdentity.deleteMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
identityId: req.params.identityId
}
});
return { identityMembership };
return {
identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId }
};
}
});
@@ -34,7 +34,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.Identities],
description: "Create identity",
description: "Create machine identity",
security: [
{
bearerAuth: []
@@ -109,7 +109,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.Identities],
description: "Update identity",
description: "Update machine identity",
security: [
{
bearerAuth: []
@@ -173,7 +173,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.Identities],
description: "Delete identity",
description: "Delete machine identity",
security: [
{
bearerAuth: []
@@ -222,7 +222,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.Identities],
description: "Get an identity by id",
description: "Get a machine identity by id",
security: [
{
bearerAuth: []
@@ -249,7 +249,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
permissions: true,
description: true
}).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true }).extend({
identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true, orgId: true }).extend({
authMethods: z.array(z.string()),
activeLockoutAuthMethods: z.array(z.string())
})
@@ -280,7 +280,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.Identities],
description: "List identities",
description: "List machine identities",
security: [
{
bearerAuth: []
@@ -330,7 +330,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.Identities],
description: "Search identities",
description: "Search machine identities",
security: [
{
bearerAuth: []
@@ -393,7 +393,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
permissions: true,
description: true
}).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true }).extend({
identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true, orgId: true }).extend({
authMethods: z.array(z.string())
})
}).array(),
@@ -427,7 +427,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "List project memberships that identity with id is part of",
description: "List project memberships that machine identity with id is part of",
security: [
{
bearerAuth: []
@@ -44,7 +44,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.TlsCertAuth],
description: "Login with TLS Certificate Auth",
description: "Login with TLS Certificate Auth for machine identity",
body: z.object({
identityId: z.string().trim().describe(TLS_CERT_AUTH.LOGIN.identityId)
}),
@@ -64,7 +64,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
throw new BadRequestError({ message: "Missing TLS certificate in header" });
}
const { identityTlsCertAuth, accessToken, identityAccessToken, identityMembershipOrg } =
const { identityTlsCertAuth, accessToken, identityAccessToken, identity } =
await server.services.identityTlsCertAuth.login({
identityId: req.body.identityId,
clientCertificate: clientCertificate as string
@@ -72,7 +72,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identity.orgId,
event: {
type: EventType.LOGIN_IDENTITY_TLS_CERT_AUTH,
metadata: {
@@ -102,7 +102,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.TlsCertAuth],
description: "Attach TLS Certificate Auth configuration onto identity",
description: "Attach TLS Certificate Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -203,7 +203,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.TlsCertAuth],
description: "Update TLS Certificate Auth configuration on identity",
description: "Update TLS Certificate Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -304,7 +304,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.TlsCertAuth],
description: "Retrieve TLS Certificate Auth configuration on identity",
description: "Retrieve TLS Certificate Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -354,7 +354,7 @@ export const registerIdentityTlsCertAuthRouter = async (server: FastifyZodProvid
schema: {
hide: false,
tags: [ApiDocsTags.TlsCertAuth],
description: "Delete TLS Certificate Auth configuration on identity",
description: "Delete TLS Certificate Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -20,7 +20,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Attach Token Auth configuration onto identity",
description: "Attach Token Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -112,7 +112,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Update Token Auth configuration on identity",
description: "Update Token Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -198,7 +198,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Retrieve Token Auth configuration on identity",
description: "Retrieve Token Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -247,7 +247,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Delete Token Auth configuration on identity",
description: "Delete Token Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -297,7 +297,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Create token for identity with Token Auth",
description: "Create token for machine identity with Token Auth",
security: [
{
bearerAuth: []
@@ -319,7 +319,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
}
},
handler: async (req) => {
const { identityTokenAuth, accessToken, identityAccessToken, identityMembershipOrg } =
const { identityTokenAuth, accessToken, identityAccessToken, identity } =
await server.services.identityTokenAuth.createTokenAuthToken({
actor: req.permission.type,
actorId: req.permission.id,
@@ -332,7 +332,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg.orgId,
orgId: identity.orgId,
event: {
type: EventType.CREATE_TOKEN_IDENTITY_TOKEN_AUTH,
metadata: {
@@ -361,7 +361,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Get tokens for identity with Token Auth",
description: "Get tokens for machine identity with Token Auth",
security: [
{
bearerAuth: []
@@ -393,7 +393,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.GET_TOKENS_IDENTITY_TOKEN_AUTH,
metadata: {
@@ -416,7 +416,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Update token for identity with Token Auth",
description: "Update token for machine identity with Token Auth",
security: [
{
bearerAuth: []
@@ -447,7 +447,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg.orgId,
orgId: identityMembershipOrg.scopeOrgId,
event: {
type: EventType.UPDATE_TOKEN_IDENTITY_TOKEN_AUTH,
metadata: {
@@ -472,7 +472,7 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
schema: {
hide: false,
tags: [ApiDocsTags.TokenAuth],
description: "Revoke token for identity with Token Auth",
description: "Revoke token for machine identity with Token Auth",
security: [
{
bearerAuth: []
@@ -32,7 +32,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Login with Universal Auth",
description: "Login with Universal Auth for machine identity",
body: z.object({
clientId: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientId),
clientSecret: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientSecret)
@@ -52,14 +52,14 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
accessToken,
identityAccessToken,
validClientSecretInfo,
identityMembershipOrg,
identity,
accessTokenTTL,
accessTokenMaxTTL
} = await server.services.identityUa.login(req.body.clientId, req.body.clientSecret, req.realIp);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
orgId: identity.orgId,
event: {
type: EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH,
metadata: {
@@ -90,7 +90,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Attach Universal Auth configuration onto identity",
description: "Attach Universal Auth configuration onto machine identity",
security: [
{
bearerAuth: []
@@ -208,7 +208,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Update Universal Auth configuration on identity",
description: "Update Universal Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -331,7 +331,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Retrieve Universal Auth configuration on identity",
description: "Retrieve Universal Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -380,7 +380,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Delete Universal Auth configuration on identity",
description: "Delete Universal Auth configuration on machine identity",
security: [
{
bearerAuth: []
@@ -429,7 +429,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Create Universal Auth Client Secret for identity",
description: "Create Universal Auth Client Secret for machine identity",
security: [
{
bearerAuth: []
@@ -487,7 +487,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "List Universal Auth Client Secrets for identity",
description: "List Universal Auth Client Secrets for machine identity",
security: [
{
bearerAuth: []
@@ -537,7 +537,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Get Universal Auth Client Secret for identity",
description: "Get Universal Auth Client Secret for machine identity",
security: [
{
bearerAuth: []
@@ -567,7 +567,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
...req.auditLogInfo,
orgId: clientSecretData.orgId,
event: {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET,
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID,
metadata: {
identityId: clientSecretData.identityId,
clientSecretId: clientSecretData.id
@@ -589,7 +589,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Revoke Universal Auth Client Secrets for identity",
description: "Revoke Universal Auth Client Secrets for machine identity",
security: [
{
bearerAuth: []
@@ -641,7 +641,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
schema: {
hide: false,
tags: [ApiDocsTags.UniversalAuth],
description: "Clear Universal Auth Lockouts for identity",
description: "Clear Universal Auth Lockouts for machine identity",
security: [
{
bearerAuth: []
+4
View File
@@ -11,6 +11,7 @@ import { registerAuthRoutes } from "./auth-router";
import { registerProjectBotRouter } from "./bot-router";
import { registerCaRouter } from "./certificate-authority-router";
import { CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP } from "./certificate-authority-routers";
import { registerCertificateProfilesRouter } from "./certificate-profiles-router";
import { registerCertRouter } from "./certificate-router";
import { registerCertificateTemplateRouter } from "./certificate-template-router";
import { registerDeprecatedProjectEnvRouter } from "./deprecated-project-env-router";
@@ -32,6 +33,7 @@ import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-rou
import { registerIdentityLdapAuthRouter } from "./identity-ldap-auth-router";
import { registerIdentityOciAuthRouter } from "./identity-oci-auth-router";
import { registerIdentityOidcAuthRouter } from "./identity-oidc-auth-router";
import { registerOrgIdentityMembershipRouter } from "./identity-org-membership-router";
import { registerIdentityProjectRouter } from "./identity-project-router";
import { registerIdentityRouter } from "./identity-router";
import { registerIdentityTlsCertAuthRouter } from "./identity-tls-cert-auth-router";
@@ -89,6 +91,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
);
await server.register(registerPasswordRouter, { prefix: "/password" });
await server.register(registerOrgRouter, { prefix: "/organization" });
await server.register(registerOrgIdentityMembershipRouter, { prefix: "/organization" });
await server.register(registerAdminRouter, { prefix: "/admin" });
await server.register(registerOrgAdminRouter, { prefix: "/organization-admin" });
await server.register(registerUserRouter, { prefix: "/user" });
@@ -146,6 +149,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
);
await pkiRouter.register(registerCertRouter, { prefix: "/certificates" });
await pkiRouter.register(registerCertificateTemplateRouter, { prefix: "/certificate-templates" });
await pkiRouter.register(registerCertificateProfilesRouter, { prefix: "/certificate-profiles" });
await pkiRouter.register(registerPkiAlertRouter, { prefix: "/alerts" });
await pkiRouter.register(registerPkiCollectionRouter, { prefix: "/collections" });
await pkiRouter.register(registerPkiSubscriberRouter, { prefix: "/subscribers" });
@@ -1,6 +1,6 @@
import { z } from "zod";
import { OrgMembershipRole, ProjectMembershipRole, UsersSchema } from "@app/db/schemas";
import { AccessScope, OrgMembershipRole, UsersSchema } from "@app/db/schemas";
import { inviteUserRateLimit, smtpRateLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -23,13 +23,6 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
.array()
.refine((val) => val.every((el) => el === el.toLowerCase()), "Email must be lowercase"),
organizationId: z.string().trim(),
projects: z
.object({
id: z.string(),
projectRoleSlug: z.string().array().default([ProjectMembershipRole.Member])
})
.array()
.optional(),
organizationRoleSlug: z.string().default(OrgMembershipRole.Member)
}),
response: {
@@ -50,15 +43,16 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
handler: async (req) => {
if (req.auth.actor !== ActorType.USER) return;
const { signupTokens: completeInviteLinks } = await server.services.org.inviteUserToOrganization({
orgId: req.body.organizationId,
actor: req.permission.type,
actorId: req.permission.id,
inviteeEmails: req.body.inviteeEmails,
projects: req.body.projects,
organizationRoleSlug: req.body.organizationRoleSlug,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
const { signUpTokens: completeInviteLinks } = await server.services.membershipUser.createMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Organization,
orgId: req.permission.orgId
},
data: {
usernames: req.body.inviteeEmails,
roles: [{ isTemporary: false, role: req.body.organizationRoleSlug }]
}
});
await server.services.telemetry.sendPostHogEvents({
@@ -86,7 +86,7 @@ export const registerOrgAdminRouter = async (server: FastifyZodProvider) => {
});
}
return { membership };
return { membership: { ...membership, userId: req.permission.id, projectId: req.params.projectId } };
}
});
};
@@ -2,10 +2,12 @@ import RE2 from "re2";
import { z } from "zod";
import {
AccessScope,
AuditLogsSchema,
GroupsSchema,
IncidentContactsSchema,
OrgMembershipsSchema,
OrgMembershipStatus,
OrgRolesSchema,
UsersSchema
} from "@app/db/schemas";
@@ -58,7 +60,14 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
organization: sanitizedOrganizationSchema
organization: sanitizedOrganizationSchema.extend({
subOrganization: z
.object({
id: z.string(),
name: z.string()
})
.optional()
})
})
}
},
@@ -68,6 +77,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
req.permission.id,
req.params.organizationId,
req.permission.authMethod,
req.permission.rootOrgId,
req.permission.orgId
);
return { organization };
@@ -263,7 +273,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
req.permission.authMethod,
req.permission.orgId
);
return { users };
return { users: users.map((el) => ({ ...el, status: el.status || OrgMembershipStatus.Accepted })) };
}
});
@@ -466,4 +476,68 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
return { groups };
}
});
server.route({
method: "GET",
url: "/users/available",
schema: {
response: {
200: z.object({
users: z
.object({
id: z.string().uuid(),
username: z.string(),
email: z.string().nullable().optional(),
firstName: z.string().nullable().optional(),
lastName: z.string().nullable().optional()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { users } = await server.services.membershipUser.listAvailableUsers({
permission: req.permission,
scopeData: {
orgId: req.permission.orgId,
scope: AccessScope.Organization
},
data: {}
});
return { users };
}
});
server.route({
method: "GET",
url: "/identities/available",
schema: {
response: {
200: z.object({
identities: z
.object({
id: z.string().uuid(),
name: z.string(),
hasDeleteProtection: z.boolean()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { identities } = await server.services.membershipIdentity.listAvailableIdentities({
permission: req.permission,
scopeData: {
orgId: req.permission.orgId,
scope: AccessScope.Organization
},
data: {}
});
return { identities };
}
});
};
@@ -0,0 +1,22 @@
import {
AWS_CERTIFICATE_MANAGER_PKI_SYNC_LIST_OPTION,
AwsCertificateManagerPkiSyncSchema,
CreateAwsCertificateManagerPkiSyncSchema,
UpdateAwsCertificateManagerPkiSyncSchema
} from "@app/services/pki-sync/aws-certificate-manager";
import { PkiSync } from "@app/services/pki-sync/pki-sync-enums";
import { registerSyncPkiEndpoints } from "./pki-sync-endpoints";
export const registerAwsCertificateManagerPkiSyncRouter = async (server: FastifyZodProvider) =>
registerSyncPkiEndpoints({
destination: PkiSync.AwsCertificateManager,
server,
responseSchema: AwsCertificateManagerPkiSyncSchema,
createSchema: CreateAwsCertificateManagerPkiSyncSchema,
updateSchema: UpdateAwsCertificateManagerPkiSyncSchema,
syncOptions: {
canImportCertificates: AWS_CERTIFICATE_MANAGER_PKI_SYNC_LIST_OPTION.canImportCertificates,
canRemoveCertificates: AWS_CERTIFICATE_MANAGER_PKI_SYNC_LIST_OPTION.canRemoveCertificates
}
});
@@ -1,9 +1,11 @@
import { PkiSync } from "@app/services/pki-sync/pki-sync-enums";
import { registerAwsCertificateManagerPkiSyncRouter } from "./aws-certificate-manager-pki-sync-router";
import { registerAzureKeyVaultPkiSyncRouter } from "./azure-key-vault-pki-sync-router";
export * from "./pki-sync-router";
export const PKI_SYNC_REGISTER_ROUTER_MAP: Record<PkiSync, (server: FastifyZodProvider) => Promise<void>> = {
[PkiSync.AzureKeyVault]: registerAzureKeyVaultPkiSyncRouter
[PkiSync.AzureKeyVault]: registerAzureKeyVaultPkiSyncRouter,
[PkiSync.AwsCertificateManager]: registerAwsCertificateManagerPkiSyncRouter
};
@@ -1,10 +1,11 @@
import { z } from "zod";
import {
OrgMembershipRole,
AccessScope,
ProjectMembershipRole,
ProjectMembershipsSchema,
ProjectUserMembershipRolesSchema,
TemporaryPermissionMode,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
@@ -14,7 +15,6 @@ import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -67,14 +67,23 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const memberships = await server.services.projectMembership.getProjectMemberships({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId
const { data: memberships } = await server.services.membershipUser.listMemberships({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
data: {}
});
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
userId: el.actorUserId as string,
projectId: req.params.projectId
}))
};
}
});
@@ -125,15 +134,30 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.getProjectMembershipById({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
id: req.params.membershipId
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const membership = await server.services.membershipUser.getMembershipByUserId({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
userId
}
});
return { membership };
return {
membership: {
...membership,
userId,
projectId: req.params.projectId
}
};
}
});
@@ -242,20 +266,17 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const usernamesAndEmails = [...req.body.emails, ...req.body.usernames];
const { projectMemberships: memberships } = await server.services.org.inviteUserToOrganization({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
inviteeEmails: usernamesAndEmails,
orgId: req.permission.orgId,
organizationRoleSlug: OrgMembershipRole.NoAccess,
projects: [
{
id: req.params.projectId,
projectRoleSlug: req.body.roleSlugs || [ProjectMembershipRole.Member]
}
]
const { memberships } = await server.services.membershipUser.createMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
data: {
roles: (req.body.roleSlugs || [ProjectMembershipRole.Member]).map((role) => ({ isTemporary: false, role })),
usernames: usernamesAndEmails
}
});
await server.services.auditLog.createAuditLog({
@@ -263,15 +284,21 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_PROJECT_MEMBER,
metadata: memberships.map(({ userId, id }) => ({
userId: userId || "",
metadata: memberships.map(({ actorUserId, id }) => ({
userId: actorUserId || "",
membershipId: id,
email: ""
}))
}
});
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
userId: el.actorUserId as string,
projectId: req.params.projectId
}))
};
}
});
@@ -305,7 +332,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
@@ -323,17 +350,28 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const roles = await server.services.projectMembership.updateProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
membershipId: req.params.membershipId,
roles: req.body.roles
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const { membership } = await server.services.membershipUser.updateMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
userId
},
data: {
roles: req.body.roles
}
});
return { roles };
return { roles: membership.roles.map((el) => ({ ...el, projectMembershipId: req.params.membershipId })) };
}
});
@@ -396,13 +434,19 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
event: {
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
userId: membership.actorUserId as string,
email: ""
}
}
});
}
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
userId: el.actorUserId as string,
projectId: req.params.projectId
}))
};
}
});
@@ -431,13 +475,22 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.deleteProjectMembership({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
membershipId: req.params.membershipId
const { userId } = await server.services.convertor.userMembershipIdToUserId(
req.params.membershipId,
AccessScope.Project,
req.permission.orgId
);
const { membership } = await server.services.membershipUser.deleteMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
userId
}
});
await server.services.auditLog.createAuditLog({
@@ -446,12 +499,19 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
event: {
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
userId: membership.actorUserId as string,
email: ""
}
}
});
return { membership };
return {
membership: {
...membership,
userId,
projectId: req.params.projectId
}
};
}
});
@@ -479,7 +539,13 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
actor: req.permission.type,
projectId: req.params.projectId
});
return { membership };
return {
membership: {
...membership,
userId: membership.actorUserId as string,
projectId: req.params.projectId
}
};
}
});
};
@@ -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 { registerLaravelForgeSyncRouter } from "./laravel-forge-sync-router";
import { registerNetlifySyncRouter } from "./netlify-sync-router";
import { registerRailwaySyncRouter } from "./railway-sync-router";
import { registerRenderSyncRouter } from "./render-sync-router";
@@ -63,5 +64,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
[SecretSync.Checkly]: registerChecklySyncRouter,
[SecretSync.DigitalOceanAppPlatform]: registerDigitalOceanAppPlatformSyncRouter,
[SecretSync.Netlify]: registerNetlifySyncRouter,
[SecretSync.Bitbucket]: registerBitbucketSyncRouter
[SecretSync.Bitbucket]: registerBitbucketSyncRouter,
[SecretSync.LaravelForge]: registerLaravelForgeSyncRouter
};
@@ -0,0 +1,17 @@
import {
CreateLaravelForgeSyncSchema,
LaravelForgeSyncSchema,
UpdateLaravelForgeSyncSchema
} from "@app/services/secret-sync/laravel-forge";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerLaravelForgeSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.LaravelForge,
server,
responseSchema: LaravelForgeSyncSchema,
createSchema: CreateLaravelForgeSyncSchema,
updateSchema: UpdateLaravelForgeSyncSchema
});
@@ -425,4 +425,42 @@ export const registerSyncSecretsEndpoints = <T extends TSecretSync, I extends TS
return { secretSync };
}
});
server.route({
method: "POST",
url: "/check-destination",
config: {
rateLimit: readLimit
},
schema: {
tags: [ApiDocsTags.SecretSyncs],
body: z.object({
destinationConfig: z.unknown(),
excludeSyncId: z.string().uuid().optional(),
projectId: z.string().uuid()
}),
response: {
200: z.object({
hasDuplicate: z.boolean(),
duplicateProjectId: z.string().uuid().optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { destinationConfig, excludeSyncId, projectId } = req.body;
const result = await server.services.secretSync.checkDuplicateDestination(
{
destinationConfig: destinationConfig as Record<string, unknown>,
destination,
excludeSyncId,
projectId
},
req.permission
);
return result;
}
});
};
@@ -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 { LaravelForgeSyncListItemSchema, LaravelForgeSyncSchema } from "@app/services/secret-sync/laravel-forge";
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";
@@ -84,7 +85,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
ChecklySyncSchema,
DigitalOceanAppPlatformSyncSchema,
NetlifySyncSchema,
BitbucketSyncSchema
BitbucketSyncSchema,
LaravelForgeSyncSchema
]);
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
@@ -117,7 +119,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
ChecklySyncListItemSchema,
SupabaseSyncListItemSchema,
NetlifySyncListItemSchema,
BitbucketSyncListItemSchema
BitbucketSyncListItemSchema,
LaravelForgeSyncListItemSchema
]);
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
@@ -0,0 +1,367 @@
import RE2 from "re2";
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import {
CertExtendedKeyUsageType,
CertKeyUsageType,
CertSubjectAlternativeNameType,
CertSubjectAttributeType
} from "@app/services/certificate-common/certificate-constants";
import { certificateTemplateV2ResponseSchema } from "@app/services/certificate-template-v2/certificate-template-v2-schemas";
const attributeTypeSchema = z.nativeEnum(CertSubjectAttributeType);
const sanTypeSchema = z.nativeEnum(CertSubjectAlternativeNameType);
const templateV2SubjectSchema = z
.object({
type: attributeTypeSchema,
allowed: z.array(z.string()).optional(),
required: z.array(z.string()).optional(),
denied: z.array(z.string()).optional()
})
.refine(
(data) => {
if (!data.allowed && !data.required && !data.denied) {
return false;
}
return true;
},
{
message: "Subject attribute must have at least one allowed, required, or denied value"
}
);
const templateV2KeyUsagesSchema = z
.object({
allowed: z.array(z.nativeEnum(CertKeyUsageType)).optional(),
required: z.array(z.nativeEnum(CertKeyUsageType)).optional(),
denied: z.array(z.nativeEnum(CertKeyUsageType)).optional()
})
.refine(
(data) => {
if (!data.allowed && !data.required && !data.denied) {
return false;
}
return true;
},
{
message: "Key usages must have at least one allowed, required, or denied value"
}
);
const templateV2ExtendedKeyUsagesSchema = z
.object({
allowed: z.array(z.nativeEnum(CertExtendedKeyUsageType)).optional(),
required: z.array(z.nativeEnum(CertExtendedKeyUsageType)).optional(),
denied: z.array(z.nativeEnum(CertExtendedKeyUsageType)).optional()
})
.refine(
(data) => {
if (!data.allowed && !data.required && !data.denied) {
return false;
}
return true;
},
{
message: "Extended key usages must have at least one allowed, required, or denied value"
}
);
const templateV2SanSchema = z
.object({
type: sanTypeSchema,
allowed: z.array(z.string()).optional(),
required: z.array(z.string()).optional(),
denied: z.array(z.string()).optional()
})
.refine(
(data) => {
if (!data.allowed && !data.required && !data.denied) {
return false;
}
return true;
},
{
message: "SAN must have at least one allowed, required, or denied value"
}
);
const templateV2ValiditySchema = z.object({
max: z
.string()
.refine(
(val) => {
if (!val) return true;
if (val.length < 2) return false;
const unit = val.slice(-1);
const number = val.slice(0, -1);
const digitRegex = new RE2("^\\d+$");
return ["d", "h", "m", "y"].includes(unit) && digitRegex.test(number);
},
{
message: "Max validity must be in format like '365d', '12m', '1y', or '24h'"
}
)
.optional()
});
const templateV2AlgorithmsSchema = z.object({
signature: z.array(z.string()).min(1, "At least one signature algorithm must be provided").optional(),
keyAlgorithm: z.array(z.string()).min(1, "At least one key algorithm must be provided").optional()
});
const createCertificateTemplateV2Schema = z.object({
projectId: z.string().min(1),
name: z.string().min(1).max(255, "Name must be between 1 and 255 characters"),
description: z.string().max(1000).optional(),
subject: z.array(templateV2SubjectSchema).optional(),
sans: z.array(templateV2SanSchema).optional(),
keyUsages: templateV2KeyUsagesSchema.optional(),
extendedKeyUsages: templateV2ExtendedKeyUsagesSchema.optional(),
algorithms: templateV2AlgorithmsSchema.optional(),
validity: templateV2ValiditySchema.optional()
});
const updateCertificateTemplateV2Schema = z.object({
name: z.string().min(1).max(255, "Name must be between 1 and 255 characters").optional(),
description: z.string().max(1000).optional(),
subject: z.array(templateV2SubjectSchema).optional(),
sans: z.array(templateV2SanSchema).optional(),
keyUsages: templateV2KeyUsagesSchema.optional(),
extendedKeyUsages: templateV2ExtendedKeyUsagesSchema.optional(),
algorithms: templateV2AlgorithmsSchema.optional(),
validity: templateV2ValiditySchema.optional()
});
export const registerCertificateTemplatesV2Router = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
body: createCertificateTemplateV2Schema,
response: {
200: z.object({
certificateTemplate: certificateTemplateV2ResponseSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { projectId, ...data } = req.body;
const certificateTemplate = await server.services.certificateTemplateV2.createTemplateV2({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod!,
actorOrgId: req.permission.orgId,
projectId,
data
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.CREATE_CERTIFICATE_TEMPLATE,
metadata: {
certificateTemplateId: certificateTemplate.id,
name: certificateTemplate.name,
projectId: certificateTemplate.projectId
}
}
});
return { certificateTemplate };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
querystring: z.object({
projectId: z.string().min(1),
offset: z.coerce.number().min(0).default(0),
limit: z.coerce.number().min(1).max(100).default(20),
search: z.string().optional()
}),
response: {
200: z.object({
certificateTemplates: certificateTemplateV2ResponseSchema.array(),
totalCount: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { templates, totalCount } = await server.services.certificateTemplateV2.listTemplatesV2({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod!,
actorOrgId: req.permission.orgId,
...req.query
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.query.projectId,
event: {
type: EventType.LIST_CERTIFICATE_TEMPLATES,
metadata: {
projectId: req.query.projectId
}
}
});
return { certificateTemplates: templates, totalCount };
}
});
server.route({
method: "GET",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: z.object({
id: z.string().uuid()
}),
response: {
200: z.object({
certificateTemplate: certificateTemplateV2ResponseSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.certificateTemplateV2.getTemplateV2ById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod!,
actorOrgId: req.permission.orgId,
templateId: req.params.id
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateTemplate.projectId,
event: {
type: EventType.GET_CERTIFICATE_TEMPLATE,
metadata: {
certificateTemplateId: certificateTemplate.id,
name: certificateTemplate.name
}
}
});
return { certificateTemplate };
}
});
server.route({
method: "PATCH",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: z.object({
id: z.string().uuid()
}),
body: updateCertificateTemplateV2Schema,
response: {
200: z.object({
certificateTemplate: certificateTemplateV2ResponseSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.certificateTemplateV2.updateTemplateV2({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod!,
actorOrgId: req.permission.orgId,
templateId: req.params.id,
data: req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateTemplate.projectId,
event: {
type: EventType.UPDATE_CERTIFICATE_TEMPLATE,
metadata: {
certificateTemplateId: certificateTemplate.id,
name: certificateTemplate.name
}
}
});
return { certificateTemplate };
}
});
server.route({
method: "DELETE",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: z.object({
id: z.string().uuid()
}),
response: {
200: z.object({
certificateTemplate: certificateTemplateV2ResponseSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.certificateTemplateV2.deleteTemplateV2({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod!,
actorOrgId: req.permission.orgId,
templateId: req.params.id
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateTemplate.projectId,
event: {
type: EventType.DELETE_CERTIFICATE_TEMPLATE,
metadata: {
certificateTemplateId: certificateTemplate.id,
name: certificateTemplate.name
}
}
});
return { certificateTemplate };
}
});
};
@@ -1,19 +1,21 @@
import { z } from "zod";
import {
AccessScope,
GroupProjectMembershipsSchema,
GroupsSchema,
ProjectMembershipRole,
ProjectUserMembershipRolesSchema,
TemporaryPermissionMode,
UsersSchema
} from "@app/db/schemas";
import { EFilterReturnedUsers } from "@app/ee/services/group/group-types";
import { ApiDocsTags, GROUPS, PROJECTS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { isUuidV4 } from "@app/lib/validator";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodProvider) => {
server.route({
@@ -54,7 +56,7 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
@@ -73,17 +75,32 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.addGroupToProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
roles: req.body.roles || [{ role: req.body.role }],
projectId: req.params.projectId,
groupIdOrName: req.params.groupIdOrName
let groupId = req.params.groupIdOrName;
if (!isUuidV4(req.params.groupIdOrName)) {
const groupDetails = await server.services.convertor.getGroupIdFromName(groupId, req.permission.orgId);
groupId = groupDetails.groupId;
}
const { membership: groupMembership } = await server.services.membershipGroup.createMembership({
permission: req.permission,
data: {
groupId,
roles: req.body.roles || [{ role: req.body.role, isTemporary: false }]
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMembership };
return {
groupMembership: {
...groupMembership,
projectId: req.params.projectId,
groupId: groupMembership.actorGroupId as string
}
};
}
});
@@ -115,7 +132,7 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryMode: z.nativeEnum(TemporaryPermissionMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
@@ -131,17 +148,22 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
}
},
handler: async (req) => {
const roles = await server.services.groupProject.updateGroupInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
groupId: req.params.groupId,
roles: req.body.roles
const { membership: groupMembership } = await server.services.membershipGroup.updateMembership({
permission: req.permission,
selector: {
groupId: req.params.groupId
},
data: {
roles: req.body.roles
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { roles };
return { roles: groupMembership.roles.map((el) => ({ ...el, projectMembershipId: groupMembership.id })) };
}
});
@@ -172,16 +194,25 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.removeGroupFromProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
groupId: req.params.groupId,
projectId: req.params.projectId
const { membership: groupMembership } = await server.services.membershipGroup.deleteMembership({
permission: req.permission,
selector: {
groupId: req.params.groupId
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMembership };
return {
groupMembership: {
...groupMembership,
projectId: req.params.projectId,
groupId: groupMembership.actorGroupId as string
}
};
}
});
@@ -233,15 +264,17 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
}
},
handler: async (req) => {
const groupMemberships = await server.services.groupProject.listGroupsInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId
const { memberships: groupMemberships } = await server.services.membershipGroup.listMemberships({
permission: req.permission,
data: {},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMemberships };
return { groupMemberships: groupMemberships.map((el) => ({ ...el, groupId: el.actorGroupId as string })) };
}
});
@@ -292,15 +325,25 @@ export const registerDeprecatedGroupProjectRouter = async (server: FastifyZodPro
}
},
handler: async (req) => {
const groupMembership = await server.services.groupProject.getGroupInProject({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.params
const { membership: groupMembership } = await server.services.membershipGroup.getMembershipByGroupId({
permission: req.permission,
selector: {
groupId: req.params.groupId
},
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
}
});
return { groupMembership };
return {
groupMembership: {
...groupMembership,
projectId: req.params.projectId,
groupId: groupMembership.actorGroupId as string
}
};
}
});
@@ -1,10 +1,12 @@
import { z } from "zod";
import {
AccessScope,
IdentitiesSchema,
IdentityProjectMembershipsSchema,
ProjectMembershipRole,
ProjectUserMembershipRolesSchema
ProjectUserMembershipRolesSchema,
TemporaryPermissionMode
} from "@app/db/schemas";
import { ApiDocsTags, ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors";
@@ -14,7 +16,6 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ProjectIdentityOrderBy } from "@app/services/identity-project/identity-project-types";
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
import { SanitizedProjectSchema } from "../sanitizedSchemas";
@@ -56,7 +57,7 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryMode: z
.nativeEnum(ProjectUserMembershipTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryRange: z
.string()
@@ -82,16 +83,22 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
const { role, roles } = req.body;
if (!role && !roles) throw new BadRequestError({ message: "You must provide either role or roles field" });
const identityMembership = await server.services.identityProject.createProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId,
roles: roles || [{ role }]
const { membership } = await server.services.membershipIdentity.createMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
data: {
identityId: req.params.identityId,
roles: roles || [{ role, isTemporary: false }]
}
});
return { identityMembership };
return {
identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId }
};
}
});
@@ -130,7 +137,7 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary),
temporaryMode: z
.nativeEnum(ProjectUserMembershipTemporaryMode)
.nativeEnum(TemporaryPermissionMode)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode),
temporaryRange: z
.string()
@@ -153,16 +160,24 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
}
},
handler: async (req) => {
const roles = await server.services.identityProject.updateProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId,
roles: req.body.roles
const { membership } = await server.services.membershipIdentity.updateMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
identityId: req.params.identityId
},
data: {
roles: req.body.roles
}
});
return { roles };
return {
roles: membership.roles.map((el) => ({ ...el, projectMembershipId: membership.id }))
};
}
});
@@ -193,15 +208,21 @@ export const registerDeprecatedIdentityProjectRouter = async (server: FastifyZod
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.deleteProjectIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId,
projectId: req.params.projectId
const { membership } = await server.services.membershipIdentity.deleteMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
selector: {
identityId: req.params.identityId
}
});
return { identityMembership };
return {
identityMembership: { ...membership, identityId: req.params.identityId, projectId: req.params.projectId }
};
}
});
@@ -1,6 +1,6 @@
import { z } from "zod";
import { OrgMembershipRole, ProjectMembershipRole, ProjectMembershipsSchema } from "@app/db/schemas";
import { AccessScope, ProjectMembershipRole, ProjectMembershipsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, PROJECT_USERS } from "@app/lib/api-docs";
import { writeLimit } from "@app/server/config/rateLimiter";
@@ -51,20 +51,17 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const usernamesAndEmails = [...req.body.emails, ...req.body.usernames];
const { projectMemberships: memberships } = await server.services.org.inviteUserToOrganization({
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
inviteeEmails: usernamesAndEmails,
orgId: req.permission.orgId,
organizationRoleSlug: OrgMembershipRole.NoAccess,
projects: [
{
id: req.params.projectId,
projectRoleSlug: req.body.roleSlugs || [ProjectMembershipRole.Member]
}
]
const { memberships } = await server.services.membershipUser.createMembership({
permission: req.permission,
scopeData: {
scope: AccessScope.Project,
orgId: req.permission.orgId,
projectId: req.params.projectId
},
data: {
usernames: usernamesAndEmails,
roles: (req.body.roleSlugs || [ProjectMembershipRole.Member]).map((role) => ({ isTemporary: false, role }))
}
});
await server.services.auditLog.createAuditLog({
@@ -72,15 +69,21 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_PROJECT_MEMBER,
metadata: memberships.map(({ userId, id }) => ({
userId: userId || "",
metadata: memberships.map(({ actorUserId, id }) => ({
userId: actorUserId || "",
membershipId: id,
email: ""
}))
}
});
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
userId: el.actorUserId as string,
projectId: req.params.projectId
}))
};
}
});
@@ -143,13 +146,19 @@ export const registerDeprecatedProjectMembershipRouter = async (server: FastifyZ
event: {
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
userId: membership.actorUserId as string,
email: ""
}
}
});
}
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
userId: el.actorUserId as string,
projectId: req.params.projectId
}))
};
}
});
};
@@ -60,7 +60,7 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
permissions: true,
description: true
}).optional(),
identity: IdentitiesSchema.pick({ name: true, id: true }).extend({
identity: IdentitiesSchema.pick({ name: true, id: true, orgId: true }).extend({
authMethods: z.array(z.string())
})
})
+3
View File
@@ -1,4 +1,5 @@
import { registerCaRouter } from "./certificate-authority-router";
import { registerCertificateTemplatesV2Router } from "./certificate-templates-v2-router";
import { registerDeprecatedGroupProjectRouter } from "./deprecated-group-project-router";
import { registerDeprecatedIdentityProjectRouter } from "./deprecated-identity-project-router";
import { registerDeprecatedProjectMembershipRouter } from "./deprecated-project-membership-router";
@@ -19,6 +20,8 @@ export const registerV2Routes = async (server: FastifyZodProvider) => {
await server.register(registerServiceTokenRouter, { prefix: "/service-token" });
await server.register(registerPasswordRouter, { prefix: "/password" });
await server.register(registerCertificateTemplatesV2Router, { prefix: "/certificate-templates" });
await server.register(
async (pkiRouter) => {
await pkiRouter.register(registerCaRouter, { prefix: "/ca" });
@@ -2,6 +2,7 @@ import { z } from "zod";
import {
OrgMembershipsSchema,
OrgMembershipStatus,
ProjectMembershipsSchema,
ProjectsSchema,
UserEncryptionKeysSchema,
@@ -63,7 +64,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
req.permission.authMethod,
req.permission.orgId
);
return { users };
return { users: users.map((el) => ({ ...el, status: el.status || OrgMembershipStatus.Accepted })) };
}
});
@@ -138,6 +139,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
response: {
200: z.object({
membership: OrgMembershipsSchema.extend({
customRoleSlug: z.string().nullish(),
metadata: z
.object({
key: z.string().trim().min(1),
@@ -168,7 +170,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
orgId: req.params.organizationId,
membershipId: req.params.membershipId
});
return { membership };
return { membership: { ...membership, status: membership.status || OrgMembershipStatus.Accepted } };
}
});
@@ -220,7 +222,14 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
actorOrgId: req.permission.orgId,
...req.body
});
return { membership };
return {
membership: {
...membership,
role: req.body.role || "",
orgId: req.params.organizationId,
status: membership.status || OrgMembershipStatus.Accepted
}
};
}
});
@@ -260,7 +269,14 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
membershipId: req.params.membershipId,
actorOrgId: req.permission.orgId
});
return { membership };
return {
membership: {
...membership,
status: membership.status || OrgMembershipStatus.Accepted,
role: "",
orgId: req.params.organizationId
}
};
}
});
@@ -302,7 +318,14 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
membershipIds: req.body.membershipIds,
actorOrgId: req.permission.orgId
});
return { memberships };
return {
memberships: memberships.map((el) => ({
...el,
status: el?.status || OrgMembershipStatus.Accepted,
role: "",
orgId: req.params.organizationId
}))
};
}
});
@@ -263,7 +263,8 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
.default("/")
.transform(prefixWithSlash) // Transformations get skipped if path is undefined
.transform(removeTrailingSlash)
.describe(FOLDERS.DELETE.path)
.describe(FOLDERS.DELETE.path),
forceDelete: z.boolean().optional().default(false).describe(FOLDERS.DELETE.forceDelete)
}),
response: {
200: z.object({
@@ -279,7 +280,8 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
idOrName: req.params.folderIdOrName
idOrName: req.params.folderIdOrName,
forceDelete: req.body.forceDelete
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
@@ -0,0 +1,346 @@
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import {
ACMESANType,
CertificateOrderStatus,
CertKeyAlgorithm,
CertSignatureAlgorithm
} from "@app/services/certificate/certificate-types";
import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators";
import {
CertExtendedKeyUsageType,
CertKeyUsageType,
CertSubjectAlternativeNameType
} from "@app/services/certificate-common/certificate-constants";
import { mapEnumsForValidation } from "@app/services/certificate-common/certificate-utils";
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
interface CertificateRequestForService {
commonName?: string;
keyUsages?: CertKeyUsageType[];
extendedKeyUsages?: CertExtendedKeyUsageType[];
altNames?: Array<{
type: CertSubjectAlternativeNameType;
value: string;
}>;
validity: {
ttl: string;
};
notBefore?: Date;
notAfter?: Date;
signatureAlgorithm?: string;
keyAlgorithm?: string;
}
const validateTtlAndDateFields = (data: { notBefore?: string; notAfter?: string; ttl?: string }) => {
const hasDateFields = data.notBefore || data.notAfter;
const hasTtl = data.ttl;
return !(hasDateFields && hasTtl);
};
const validateDateOrder = (data: { notBefore?: string; notAfter?: string }) => {
if (data.notBefore && data.notAfter) {
const notBefore = new Date(data.notBefore);
const notAfter = new Date(data.notAfter);
return notBefore < notAfter;
}
return true;
};
export const registerCertificatesRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/issue-certificate",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificates],
body: z
.object({
profileId: z.string().uuid(),
commonName: validateTemplateRegexField.optional(),
ttl: z
.string()
.trim()
.min(1, "TTL cannot be empty")
.refine((val) => ms(val) > 0, "TTL must be a positive number"),
keyUsages: z.nativeEnum(CertKeyUsageType).array().optional(),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsageType).array().optional(),
notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional(),
altNames: z
.array(
z.object({
type: z.nativeEnum(CertSubjectAlternativeNameType),
value: z.string().min(1, "SAN value cannot be empty")
})
)
.optional(),
signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm).optional(),
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).optional()
})
.refine(validateTtlAndDateFields, {
message:
"Cannot specify both TTL and notBefore/notAfter. Use either TTL for duration-based validity or notBefore/notAfter for explicit date range."
})
.refine(validateDateOrder, {
message: "notBefore must be earlier than notAfter"
}),
response: {
200: z.object({
certificate: z.string().trim(),
issuingCaCertificate: z.string().trim(),
certificateChain: z.string().trim(),
privateKey: z.string().trim().optional(),
serialNumber: z.string().trim(),
certificateId: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateRequestForService: CertificateRequestForService = {
commonName: req.body.commonName,
keyUsages: req.body.keyUsages,
extendedKeyUsages: req.body.extendedKeyUsages,
altNames: req.body.altNames,
validity: {
ttl: req.body.ttl
},
notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined,
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined,
signatureAlgorithm: req.body.signatureAlgorithm,
keyAlgorithm: req.body.keyAlgorithm
};
const mappedCertificateRequest = mapEnumsForValidation(certificateRequestForService);
const data = await server.services.certificateV3.issueCertificateFromProfile({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.body.profileId,
certificateRequest: mappedCertificateRequest
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: data.projectId,
event: {
type: EventType.ISSUE_CERTIFICATE_FROM_PROFILE,
metadata: {
certificateProfileId: req.body.profileId,
certificateId: data.certificateId,
commonName: req.body.commonName || "",
profileName: data.profileName
}
}
});
return data;
}
});
server.route({
method: "POST",
url: "/sign-certificate",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificates],
body: z
.object({
profileId: z.string().uuid(),
csr: z.string().trim().min(1, "CSR cannot be empty").max(4096, "CSR cannot exceed 4096 characters"),
ttl: z
.string()
.trim()
.min(1, "TTL cannot be empty")
.refine((val) => ms(val) > 0, "TTL must be a positive number"),
notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional(),
signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm).optional(),
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).optional()
})
.refine(validateTtlAndDateFields, {
message:
"Cannot specify both TTL and notBefore/notAfter. Use either TTL for duration-based validity or notBefore/notAfter for explicit date range."
})
.refine(validateDateOrder, {
message: "notBefore must be earlier than notAfter"
}),
response: {
200: z.object({
certificate: z.string().trim(),
issuingCaCertificate: z.string().trim(),
certificateChain: z.string().trim(),
serialNumber: z.string().trim(),
certificateId: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.certificateV3.signCertificateFromProfile({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.body.profileId,
csr: req.body.csr,
validity: {
ttl: req.body.ttl
},
notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined,
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined,
signatureAlgorithm: req.body.signatureAlgorithm,
keyAlgorithm: req.body.keyAlgorithm
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: data.projectId,
event: {
type: EventType.SIGN_CERTIFICATE_FROM_PROFILE,
metadata: {
certificateProfileId: req.body.profileId,
certificateId: data.certificateId,
profileName: data.profileName,
commonName: ""
}
}
});
return data;
}
});
server.route({
method: "POST",
url: "/order-certificate",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificates],
body: z
.object({
profileId: z.string().uuid(),
subjectAlternativeNames: z
.array(
z.object({
type: z.nativeEnum(ACMESANType),
value: z
.string()
.trim()
.min(1, "SAN value cannot be empty")
.max(255, "SAN value must be less than 255 characters")
})
)
.min(1, "At least one subject alternative name must be provided"),
ttl: z
.string()
.trim()
.min(1, "TTL cannot be empty")
.refine((val) => ms(val) > 0, "TTL must be a positive number"),
keyUsages: z.nativeEnum(CertKeyUsageType).array().optional(),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsageType).array().optional(),
notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional(),
commonName: validateTemplateRegexField.optional(),
signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm).optional(),
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).optional()
})
.refine(validateTtlAndDateFields, {
message:
"Cannot specify both TTL and notBefore/notAfter. Use either TTL for duration-based validity or notBefore/notAfter for explicit date range."
})
.refine(validateDateOrder, {
message: "notBefore must be earlier than notAfter"
}),
response: {
200: z.object({
orderId: z.string(),
status: z.nativeEnum(CertificateOrderStatus),
subjectAlternativeNames: z.array(
z.object({
type: z.nativeEnum(ACMESANType),
value: z.string(),
status: z.nativeEnum(CertificateOrderStatus)
})
),
authorizations: z.array(
z.object({
identifier: z.object({
type: z.nativeEnum(ACMESANType),
value: z.string()
}),
status: z.nativeEnum(CertificateOrderStatus),
expires: z.string().optional(),
challenges: z.array(
z.object({
type: z.string(),
status: z.nativeEnum(CertificateOrderStatus),
url: z.string(),
token: z.string()
})
)
})
),
finalize: z.string(),
certificate: z.string().optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.certificateV3.orderCertificateFromProfile({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.body.profileId,
certificateOrder: {
altNames: req.body.subjectAlternativeNames,
validity: {
ttl: req.body.ttl
},
commonName: req.body.commonName,
keyUsages: req.body.keyUsages,
extendedKeyUsages: req.body.extendedKeyUsages,
notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined,
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined,
signatureAlgorithm: req.body.signatureAlgorithm,
keyAlgorithm: req.body.keyAlgorithm
}
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: data.projectId,
event: {
type: EventType.ORDER_CERTIFICATE_FROM_PROFILE,
metadata: {
certificateProfileId: req.body.profileId,
orderId: data.orderId,
profileName: data.profileName
}
}
});
return data;
}
});
};
@@ -7,6 +7,7 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import {
ExternalMigrationProviders,
VaultImportStatus,
VaultMappingType
} from "@app/services/external-migration/external-migration-types";
@@ -113,4 +114,366 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
return { enabled };
}
});
server.route({
method: "GET",
url: "/vault/configs",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({
configs: z
.object({
id: z.string(),
orgId: z.string(),
namespace: z.string(),
connectionId: z.string().nullish(),
createdAt: z.date(),
updatedAt: z.date()
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const configs = await server.services.migration.getVaultExternalMigrationConfigs({
actor: req.permission
});
return { configs };
}
});
server.route({
method: "POST",
url: "/vault/configs",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
connectionId: z.string(),
namespace: z.string()
}),
response: {
200: z.object({
config: z.object({
id: z.string(),
orgId: z.string(),
namespace: z.string(),
connectionId: z.string().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const config = await server.services.migration.createVaultExternalMigration({
...req.body,
actor: req.permission
});
return { config };
}
});
server.route({
method: "PUT",
url: "/vault/configs/:id",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
id: z.string()
}),
body: z.object({
connectionId: z.string(),
namespace: z.string()
}),
response: {
200: z.object({
config: z.object({
id: z.string(),
orgId: z.string(),
namespace: z.string(),
connectionId: z.string().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const config = await server.services.migration.updateVaultExternalMigration({
id: req.params.id,
...req.body,
actor: req.permission
});
return { config };
}
});
server.route({
method: "DELETE",
url: "/vault/configs/:id",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
id: z.string()
}),
response: {
200: z.object({
config: z.object({
id: z.string(),
orgId: z.string(),
namespace: z.string(),
connectionId: z.string().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const config = await server.services.migration.deleteVaultExternalMigration({
id: req.params.id,
actor: req.permission
});
return { config };
}
});
server.route({
method: "GET",
url: "/vault/namespaces",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({
namespaces: z.array(z.object({ id: z.string(), name: z.string() }))
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const namespaces = await server.services.migration.getVaultNamespaces({
actor: req.permission
});
return { namespaces };
}
});
server.route({
method: "GET",
url: "/vault/policies",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
namespace: z.string()
}),
response: {
200: z.object({
policies: z.array(z.object({ name: z.string(), rules: z.string() }))
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const policies = await server.services.migration.getVaultPolicies({
actor: req.permission,
namespace: req.query.namespace
});
return { policies };
}
});
server.route({
method: "GET",
url: "/vault/mounts",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
namespace: z.string()
}),
response: {
200: z.object({
mounts: z.array(z.object({ path: z.string(), type: z.string(), version: z.string().nullish() }))
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const mounts = await server.services.migration.getVaultMounts({
actor: req.permission,
namespace: req.query.namespace
});
return { mounts };
}
});
server.route({
method: "GET",
url: "/vault/auth-mounts",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
namespace: z.string(),
authType: z.string().optional()
}),
response: {
200: z.object({
mounts: z.array(z.object({ path: z.string(), type: z.string() }))
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const mounts = await server.services.migration.getVaultAuthMounts({
actor: req.permission,
namespace: req.query.namespace,
authType: req.query.authType
});
return { mounts };
}
});
server.route({
method: "POST",
url: "/vault/import-secrets",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
projectId: z.string(),
environment: z.string(),
secretPath: z.string(),
vaultNamespace: z.string(),
vaultSecretPath: z.string()
}),
response: {
200: z.object({
status: z.nativeEnum(VaultImportStatus)
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const result = await server.services.migration.importVaultSecrets({
actor: req.permission,
auditLogInfo: req.auditLogInfo,
...req.body
});
return result;
}
});
server.route({
method: "GET",
url: "/vault/secret-paths",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
namespace: z.string(),
mountPath: z.string()
}),
response: {
200: z.object({
secretPaths: z.string().array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const secretPaths = await server.services.migration.getVaultSecretPaths({
actor: req.permission,
namespace: req.query.namespace,
mountPath: req.query.mountPath
});
return { secretPaths };
}
});
server.route({
method: "GET",
url: "/vault/auth-roles/kubernetes",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
namespace: z.string(),
mountPath: z.string()
}),
response: {
200: z.object({
roles: z.array(
z.object({
name: z.string(),
mountPath: z.string(),
bound_service_account_names: z.array(z.string()),
bound_service_account_namespaces: z.array(z.string()),
token_ttl: z.number().optional(),
token_max_ttl: z.number().optional(),
token_policies: z.array(z.string()).optional(),
token_bound_cidrs: z.array(z.string()).optional(),
token_explicit_max_ttl: z.number().optional(),
token_no_default_policy: z.boolean().optional(),
token_num_uses: z.number().optional(),
token_period: z.number().optional(),
token_type: z.string().optional(),
audience: z.string().optional(),
alias_name_source: z.string().optional(),
config: z.object({
kubernetes_host: z.string(),
kubernetes_ca_cert: z.string().optional(),
issuer: z.string().optional(),
disable_iss_validation: z.boolean().optional(),
disable_local_ca_jwt: z.boolean().optional()
})
})
)
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const roles = await server.services.migration.getVaultKubernetesAuthRoles({
actor: req.permission,
namespace: req.query.namespace,
mountPath: req.query.mountPath
});
return { roles };
}
});
};
+2
View File
@@ -1,3 +1,4 @@
import { registerCertificatesRouter } from "./certificates-router";
import { registerDeprecatedSecretRouter } from "./deprecated-secret-router";
import { registerExternalMigrationRouter } from "./external-migration-router";
import { registerLoginRouter } from "./login-router";
@@ -10,4 +11,5 @@ export const registerV3Routes = async (server: FastifyZodProvider) => {
await server.register(registerUserRouter, { prefix: "/users" });
await server.register(registerDeprecatedSecretRouter, { prefix: "/secrets" });
await server.register(registerExternalMigrationRouter, { prefix: "/external-migration" });
await server.register(registerCertificatesRouter, { prefix: "/certificates" });
};