feat: completed all workspace prefix routes

This commit is contained in:
=
2025-09-12 16:04:17 +05:30
parent cd2a39b374
commit fd2b067e14
24 changed files with 1207 additions and 316 deletions

View File

@@ -146,7 +146,7 @@ export enum EventType {
MOVE_SECRETS = "move-secrets",
DELETE_SECRET = "delete-secret",
DELETE_SECRETS = "delete-secrets",
GET_WORKSPACE_KEY = "get-workspace-key",
GET_PROJECT_KEY = "get-project-key",
AUTHORIZE_INTEGRATION = "authorize-integration",
UPDATE_INTEGRATION_AUTH = "update-integration-auth",
UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
@@ -249,9 +249,9 @@ export enum EventType {
UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment",
GET_ENVIRONMENT = "get-environment",
ADD_WORKSPACE_MEMBER = "add-workspace-member",
ADD_BATCH_WORKSPACE_MEMBER = "add-workspace-members",
REMOVE_WORKSPACE_MEMBER = "remove-workspace-member",
ADD_PROJECT_MEMBER = "add-project-member",
ADD_BATCH_PROJECT_MEMBER = "add-project-members",
REMOVE_PROJECT_MEMBER = "remove-project-member",
CREATE_FOLDER = "create-folder",
UPDATE_FOLDER = "update-folder",
DELETE_FOLDER = "delete-folder",
@@ -264,8 +264,8 @@ export enum EventType {
CREATE_SECRET_IMPORT = "create-secret-import",
UPDATE_SECRET_IMPORT = "update-secret-import",
DELETE_SECRET_IMPORT = "delete-secret-import",
UPDATE_USER_WORKSPACE_ROLE = "update-user-workspace-role",
UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS = "update-user-workspace-denied-permissions",
UPDATE_USER_PROJECT_ROLE = "update-user-project-role",
UPDATE_USER_PROJECT_DENIED_PERMISSIONS = "update-user-project-denied-permissions",
SECRET_APPROVAL_MERGED = "secret-approval-merged",
SECRET_APPROVAL_REQUEST = "secret-approval-request",
SECRET_APPROVAL_CLOSED = "secret-approval-closed",
@@ -664,8 +664,8 @@ interface DeleteSecretBatchEvent {
};
}
interface GetWorkspaceKeyEvent {
type: EventType.GET_WORKSPACE_KEY;
interface GetProjectKeyEvent {
type: EventType.GET_PROJECT_KEY;
metadata: {
keyId: string;
};
@@ -1557,24 +1557,24 @@ interface DeleteEnvironmentEvent {
};
}
interface AddWorkspaceMemberEvent {
type: EventType.ADD_WORKSPACE_MEMBER;
interface AddProjectMemberEvent {
type: EventType.ADD_PROJECT_MEMBER;
metadata: {
userId: string;
email: string;
};
}
interface AddBatchWorkspaceMemberEvent {
type: EventType.ADD_BATCH_WORKSPACE_MEMBER;
interface AddBatchProjectMemberEvent {
type: EventType.ADD_BATCH_PROJECT_MEMBER;
metadata: Array<{
userId: string;
email: string;
}>;
}
interface RemoveWorkspaceMemberEvent {
type: EventType.REMOVE_WORKSPACE_MEMBER;
interface RemoveProjectMemberEvent {
type: EventType.REMOVE_PROJECT_MEMBER;
metadata: {
userId: string;
email: string;
@@ -1713,7 +1713,7 @@ interface DeleteSecretImportEvent {
}
interface UpdateUserRole {
type: EventType.UPDATE_USER_WORKSPACE_ROLE;
type: EventType.UPDATE_USER_PROJECT_ROLE;
metadata: {
userId: string;
email: string;
@@ -1723,7 +1723,7 @@ interface UpdateUserRole {
}
interface UpdateUserDeniedPermissions {
type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS;
type: EventType.UPDATE_USER_PROJECT_DENIED_PERMISSIONS;
metadata: {
userId: string;
email: string;
@@ -3477,7 +3477,7 @@ export type Event =
| MoveSecretsEvent
| DeleteSecretEvent
| DeleteSecretBatchEvent
| GetWorkspaceKeyEvent
| GetProjectKeyEvent
| AuthorizeIntegrationEvent
| UpdateIntegrationAuthEvent
| UnauthorizeIntegrationEvent
@@ -3566,9 +3566,9 @@ export type Event =
| GetEnvironmentEvent
| UpdateEnvironmentEvent
| DeleteEnvironmentEvent
| AddWorkspaceMemberEvent
| AddBatchWorkspaceMemberEvent
| RemoveWorkspaceMemberEvent
| AddProjectMemberEvent
| AddBatchProjectMemberEvent
| RemoveProjectMemberEvent
| CreateFolderEvent
| UpdateFolderEvent
| DeleteFolderEvent

View File

@@ -240,7 +240,7 @@ export const registerDepreciatedProjectMembershipRouter = async (server: Fastify
projectId: req.params.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_WORKSPACE_MEMBER,
type: EventType.ADD_BATCH_PROJECT_MEMBER,
metadata: data.map(({ userId }) => ({
userId: userId || "",
email: ""
@@ -365,7 +365,7 @@ export const registerDepreciatedProjectMembershipRouter = async (server: Fastify
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.REMOVE_WORKSPACE_MEMBER,
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
email: ""

View File

@@ -15,9 +15,11 @@ import { registerCertRouter } from "./certificate-router";
import { registerCertificateTemplateRouter } from "./certificate-template-router";
import { registerDepreciatedProjectEnvRouter } from "./depreciated-project-env-router";
import { registerDepreciatedProjectMembershipRouter } from "./depreciated-project-membership-router";
import { registerDepreciatedProjectRouter } from "./depreciated-project-router";
import { registerDepreciatedSecretTagRouter } from "./depreciated-secret-tag-router";
import { registerEventRouter } from "./event-router";
import { registerExternalGroupOrgRoleMappingRouter } from "./external-group-org-role-mapping-router";
import { registerGroupProjectRouter } from "./group-project-router";
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
import { registerIdentityAliCloudAuthRouter } from "./identity-alicloud-auth-router";
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
@@ -46,10 +48,10 @@ import { registerPkiSubscriberRouter } from "./pki-subscriber-router";
import { registerProjectEnvRouter } from "./project-env-router";
import { registerProjectKeyRouter } from "./project-key-router";
import { registerProjectMembershipRouter } from "./project-membership-router";
import { registerDepreciatedProjectRouter } from "./depreciated-project-router";
import { registerProjectRouter } from "./project-router";
import { SECRET_REMINDER_REGISTER_ROUTER_MAP } from "./reminder-routers";
import { registerSecretFolderRouter } from "./secret-folder-router";
import { registerSecretImportRouter } from "./secret-import-router";
import { registerSecretRequestsRouter } from "./secret-requests-router";
import { registerSecretSharingRouter } from "./secret-sharing-router";
import { registerSecretTagRouter } from "./secret-tag-router";
@@ -60,7 +62,7 @@ import { registerUserEngagementRouter } from "./user-engagement-router";
import { registerUserRouter } from "./user-router";
import { registerWebhookRouter } from "./webhook-router";
import { registerWorkflowIntegrationRouter } from "./workflow-integration-router";
import { registerSecretImportRouter } from "./secret-import-router";
import { registerIdentityProjectRouter } from "./identity-project-router";
export const registerV1Routes = async (server: FastifyZodProvider) => {
await server.register(registerSsoRouter, { prefix: "/sso" });
@@ -121,6 +123,8 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
await projectRouter.register(registerProjectMembershipRouter);
await projectRouter.register(registerProjectEnvRouter);
await projectRouter.register(registerSecretTagRouter);
await projectRouter.register(registerGroupProjectRouter);
await projectRouter.register(registerIdentityProjectRouter);
},
{ prefix: "/projects" }
);

View File

@@ -1,7 +1,8 @@
import { z } from "zod";
import {
OrgMembershipsSchema,
OrgMembershipRole,
ProjectMembershipRole,
ProjectMembershipsSchema,
ProjectUserMembershipRolesSchema,
UserEncryptionKeysSchema,
@@ -206,50 +207,72 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectUsers],
description: "Invite members to project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim()
projectId: z.string().describe(PROJECT_USERS.INVITE_MEMBER.projectId)
}),
body: z.object({
members: z
.object({
orgMembershipId: z.string().trim(),
workspaceEncryptedKey: z.string().trim(),
workspaceEncryptedNonce: z.string().trim()
})
emails: z
.string()
.email()
.array()
.min(1)
.default([])
.describe(PROJECT_USERS.INVITE_MEMBER.emails)
.refine((val) => val.every((el) => el === el.toLowerCase()), "Email must be lowercase"),
usernames: z
.string()
.array()
.default([])
.describe(PROJECT_USERS.INVITE_MEMBER.usernames)
.refine((val) => val.every((el) => el === el.toLowerCase()), "Username must be lowercase"),
roleSlugs: z.string().array().min(1).optional().describe(PROJECT_USERS.INVITE_MEMBER.roleSlugs)
}),
response: {
200: z.object({
success: z.boolean(),
data: OrgMembershipsSchema.array()
memberships: ProjectMembershipsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.projectMembership.addUsersToProject({
actorId: req.permission.id,
actor: req.permission.type,
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,
projectId: req.params.projectId,
members: req.body.members
actor: req.permission.type,
inviteeEmails: usernamesAndEmails,
orgId: req.permission.orgId,
organizationRoleSlug: OrgMembershipRole.NoAccess,
projects: [
{
id: req.params.projectId,
projectRoleSlug: req.body.roleSlugs || [ProjectMembershipRole.Member]
}
]
});
await server.services.auditLog.createAuditLog({
projectId: req.params.projectId,
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_WORKSPACE_MEMBER,
metadata: data.map(({ userId }) => ({
type: EventType.ADD_BATCH_PROJECT_MEMBER,
metadata: memberships.map(({ userId, id }) => ({
userId: userId || "",
membershipId: id,
email: ""
}))
}
});
return { data, success: true };
return { memberships };
}
});
@@ -311,23 +334,79 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
roles: req.body.roles
});
// await server.services.auditLog.createAuditLog({
// ...req.auditLogInfo,
// projectId: req.params.projectId,
// event: {
// type: EventType.UPDATE_USER_WORKSPACE_ROLE,
// metadata: {
// userId: membership.userId,
// newRole: req.body.role,
// oldRole: membership.role,
// email: ""
// }
// }
// });
return { roles };
}
});
server.route({
method: "DELETE",
url: "/:projectId/memberships",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectUsers],
description: "Remove members from project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().describe(PROJECT_USERS.REMOVE_MEMBER.projectId)
}),
body: z.object({
emails: z
.string()
.email()
.array()
.default([])
.describe(PROJECT_USERS.REMOVE_MEMBER.emails)
.refine((val) => val.every((el) => el === el.toLowerCase()), "Email must be lowercase"),
usernames: z
.string()
.array()
.default([])
.describe(PROJECT_USERS.REMOVE_MEMBER.usernames)
.refine((val) => val.every((el) => el === el.toLowerCase()), "Username must be lowercase")
}),
response: {
200: z.object({
memberships: ProjectMembershipsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const memberships = await server.services.projectMembership.deleteProjectMemberships({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
emails: req.body.emails,
usernames: req.body.usernames
});
for (const membership of memberships) {
// eslint-disable-next-line no-await-in-loop
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.projectId,
event: {
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
email: ""
}
}
});
}
return { memberships };
}
});
server.route({
method: "DELETE",
url: "/:projectId/memberships/:membershipId",
@@ -366,7 +445,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
...req.auditLogInfo,
projectId: req.params.projectId,
event: {
type: EventType.REMOVE_WORKSPACE_MEMBER,
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
email: ""

View File

@@ -29,6 +29,7 @@ import { WorkflowIntegration } from "@app/services/workflow-integration/workflow
import { integrationAuthPubSchema, SanitizedProjectSchema } from "../sanitizedSchemas";
import { sanitizedServiceTokenSchema } from "../v2/service-token-router";
import { slugSchema } from "@app/server/lib/schemas";
const projectWithEnv = SanitizedProjectSchema.merge(
z.object({
@@ -117,6 +118,83 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Projects],
description: "Create a new project",
security: [
{
bearerAuth: []
}
],
body: z.object({
projectName: z.string().trim().describe(PROJECTS.CREATE.projectName),
projectDescription: z.string().trim().optional().describe(PROJECTS.CREATE.projectDescription),
slug: slugSchema({ min: 5, max: 36 }).optional().describe(PROJECTS.CREATE.slug),
kmsKeyId: z.string().optional(),
template: slugSchema({ field: "Template Name", max: 64 })
.optional()
.default(InfisicalProjectTemplate.Default)
.describe(PROJECTS.CREATE.template),
type: z.nativeEnum(ProjectType).default(ProjectType.SecretManager),
shouldCreateDefaultEnvs: z.boolean().optional().default(true)
}),
response: {
200: z.object({
project: projectWithEnv
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const project = await server.services.project.createProject({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
workspaceName: req.body.projectName,
workspaceDescription: req.body.projectDescription,
slug: req.body.slug,
kmsKeyId: req.body.kmsKeyId,
template: req.body.template,
type: req.body.type,
createDefaultEnvs: req.body.shouldCreateDefaultEnvs
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.ProjectCreated,
distinctId: getTelemetryDistinctId(req),
organizationId: req.permission.orgId,
properties: {
orgId: project.orgId,
name: project.name,
...req.auditLogInfo
}
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: project.id,
event: {
type: EventType.CREATE_PROJECT,
metadata: {
...req.body,
name: req.body.projectName
}
}
});
return { project };
}
});
server.route({
method: "GET",
url: "/",
@@ -195,6 +273,47 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
});
// TODO(depri): confirm with the team
server.route({
method: "GET",
url: "/slug/:slug",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Projects],
description: "Get project details by slug",
security: [
{
bearerAuth: []
}
],
params: z.object({
slug: slugSchema({ max: 36 }).describe("The slug of the project to get.")
}),
response: {
200: projectWithEnv
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const project = await server.services.project.getAProject({
filter: {
slug: req.params.slug,
orgId: req.permission.orgId,
type: ProjectFilterType.SLUG
},
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type
});
return project;
}
});
server.route({
method: "DELETE",
url: "/:projectId",
@@ -246,6 +365,58 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
});
/* Delete a project by slug */
server.route({
method: "DELETE",
url: "/slug/:slug",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.Projects],
description: "Delete project",
security: [
{
bearerAuth: []
}
],
params: z.object({
slug: slugSchema({ min: 5, max: 36 }).describe("The slug of the project to delete.")
}),
response: {
200: SanitizedProjectSchema
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const project = await server.services.project.deleteProject({
filter: {
type: ProjectFilterType.SLUG,
slug: req.params.slug,
orgId: req.permission.orgId
},
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
actor: req.permission.type
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
projectId: project.id,
event: {
type: EventType.DELETE_PROJECT,
metadata: project
}
});
return project;
}
});
server.route({
method: "PATCH",
url: "/:projectId",
@@ -862,7 +1033,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
params: z.object({
projectId: z.string().trim()
}),
body: z.discriminatedUnion("integration", [
z.object({
integration: z.literal(WorkflowIntegration.SLACK),
@@ -1071,4 +1241,66 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
return { message: "Project access request has been send to project admins" };
}
});
/* Start upgrade of a project */
server.route({
method: "POST",
url: "/:projectId/upgrade",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
}),
body: z.object({
userPrivateKey: z.string().trim()
}),
response: {
200: z.void()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.project.upgradeProject({
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
projectId: req.params.projectId,
userPrivateKey: req.body.userPrivateKey
});
}
});
/* Get upgrade status of project */
server.route({
url: "/:projectId/upgrade/status",
method: "GET",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
status: z.string().nullable()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const status = await server.services.project.getProjectUpgradeStatus({
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
actor: req.permission.type,
actorId: req.permission.id
});
return { status };
}
});
};

View File

@@ -0,0 +1,363 @@
import { z } from "zod";
import {
GroupProjectMembershipsSchema,
GroupsSchema,
ProjectMembershipRole,
ProjectUserMembershipRolesSchema,
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 { 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 registerDepreciatedGroupProjectRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:projectId/groups/:groupIdOrName",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectGroups],
description: "Add group to project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.ADD_GROUP_TO_PROJECT.projectId),
groupIdOrName: z.string().trim().describe(PROJECTS.ADD_GROUP_TO_PROJECT.groupIdOrName)
}),
body: z
.object({
role: z
.string()
.trim()
.min(1)
.default(ProjectMembershipRole.NoAccess)
.describe(PROJECTS.ADD_GROUP_TO_PROJECT.role),
roles: z
.array(
z.union([
z.object({
role: z.string(),
isTemporary: z.literal(false).default(false)
}),
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
])
)
.optional()
})
.refine((data) => data.role || data.roles, {
message: "Either role or roles must be present",
path: ["role", "roles"]
}),
response: {
200: z.object({
groupMembership: GroupProjectMembershipsSchema
})
}
},
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
});
return { groupMembership };
}
});
server.route({
method: "PATCH",
url: "/:projectId/groups/:groupId",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectGroups],
description: "Update group in project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.projectId),
groupId: z.string().trim().describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.groupId)
}),
body: z.object({
roles: z
.array(
z.union([
z.object({
role: z.string(),
isTemporary: z.literal(false).default(false)
}),
z.object({
role: z.string(),
isTemporary: z.literal(true),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
temporaryAccessStartTime: z.string().datetime()
})
])
)
.min(1)
.describe(PROJECTS.UPDATE_GROUP_IN_PROJECT.roles)
}),
response: {
200: z.object({
roles: ProjectUserMembershipRolesSchema.array()
})
}
},
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
});
return { roles };
}
});
server.route({
method: "DELETE",
url: "/:projectId/groups/:groupId",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectGroups],
description: "Remove group from project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.REMOVE_GROUP_FROM_PROJECT.projectId),
groupId: z.string().trim().describe(PROJECTS.REMOVE_GROUP_FROM_PROJECT.groupId)
}),
response: {
200: z.object({
groupMembership: GroupProjectMembershipsSchema
})
}
},
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
});
return { groupMembership };
}
});
server.route({
method: "GET",
url: "/:projectId/groups",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectGroups],
description: "Return list of groups in project",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECTS.LIST_GROUPS_IN_PROJECT.projectId)
}),
response: {
200: z.object({
groupMemberships: z
.object({
id: z.string(),
groupId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
),
group: GroupsSchema.pick({ name: true, id: true, slug: true })
})
.array()
})
}
},
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
});
return { groupMemberships };
}
});
server.route({
method: "GET",
url: "/:projectId/groups/:groupId",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectGroups],
description: "Return project group",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim(),
groupId: z.string().trim()
}),
response: {
200: z.object({
groupMembership: z.object({
id: z.string(),
groupId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
),
group: GroupsSchema.pick({ name: true, id: true, slug: true })
})
})
}
},
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
});
return { groupMembership };
}
});
server.route({
method: "GET",
url: "/:projectId/groups/:groupId/users",
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.ProjectGroups],
description: "Return project group users",
params: z.object({
projectId: z.string().trim().describe(GROUPS.LIST_USERS.projectId),
groupId: z.string().trim().describe(GROUPS.LIST_USERS.id)
}),
querystring: z.object({
offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset),
limit: z.coerce.number().min(1).max(100).default(10).describe(GROUPS.LIST_USERS.limit),
username: z.string().trim().optional().describe(GROUPS.LIST_USERS.username),
search: z.string().trim().optional().describe(GROUPS.LIST_USERS.search),
filter: z.nativeEnum(EFilterReturnedUsers).optional().describe(GROUPS.LIST_USERS.filterUsers)
}),
response: {
200: z.object({
users: UsersSchema.pick({
email: true,
username: true,
firstName: true,
lastName: true,
id: true
})
.merge(
z.object({
isPartOfGroup: z.boolean(),
joinedGroupAt: z.date().nullable()
})
)
.array(),
totalCount: z.number()
})
}
},
handler: async (req) => {
const { users, totalCount } = await server.services.groupProject.listProjectGroupUsers({
id: req.params.groupId,
projectId: req.params.projectId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query
});
return { users, totalCount };
}
});
};

View File

@@ -0,0 +1,418 @@
import { z } from "zod";
import {
IdentitiesSchema,
IdentityProjectMembershipsSchema,
ProjectMembershipRole,
ProjectUserMembershipRolesSchema
} from "@app/db/schemas";
import { ApiDocsTags, ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
import { OrderByDirection } from "@app/lib/types";
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";
export const registerDepreciatedIdentityProjectRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:projectId/identity-memberships/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectIdentities],
description: "Create project identity membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim(),
identityId: z.string().trim()
}),
body: z.object({
// @depreciated
role: z.string().trim().optional().default(ProjectMembershipRole.NoAccess),
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(ProjectUserMembershipTemporaryMode)
.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)
.optional()
}),
response: {
200: z.object({
identityMembership: IdentityProjectMembershipsSchema
})
}
},
handler: async (req) => {
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 }]
});
return { identityMembership };
}
});
server.route({
method: "PATCH",
url: "/:projectId/identity-memberships/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectIdentities],
description: "Update project identity memberships",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.projectId),
identityId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.identityId)
}),
body: z.object({
roles: z
.array(
z.union([
z.object({
role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z
.literal(false)
.default(false)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary)
}),
z.object({
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)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode),
temporaryRange: z
.string()
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryRange),
temporaryAccessStartTime: z
.string()
.datetime()
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryAccessStartTime)
})
])
)
.min(1)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.description)
}),
response: {
200: z.object({
roles: ProjectUserMembershipRolesSchema.array()
})
}
},
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
});
return { roles };
}
});
server.route({
method: "DELETE",
url: "/:projectId/identity-memberships/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectIdentities],
description: "Delete project identity memberships",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.projectId),
identityId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.identityId)
}),
response: {
200: z.object({
identityMembership: IdentityProjectMembershipsSchema
})
}
},
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
});
return { identityMembership };
}
});
server.route({
method: "GET",
url: "/:projectId/identity-memberships",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectIdentities],
description: "Return project identity memberships",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.projectId)
}),
querystring: z.object({
offset: z.coerce
.number()
.min(0)
.default(0)
.describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.offset)
.optional(),
limit: z.coerce
.number()
.min(1)
.max(20000) // TODO: temp limit until combobox added to add identity to project modal, reduce once added
.default(100)
.describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.limit)
.optional(),
orderBy: z
.nativeEnum(ProjectIdentityOrderBy)
.default(ProjectIdentityOrderBy.Name)
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderBy)
.optional(),
orderDirection: z
.nativeEnum(OrderByDirection)
.default(OrderByDirection.ASC)
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderDirection)
.optional(),
search: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.search).optional()
}),
response: {
200: z.object({
identityMemberships: z
.object({
id: z.string(),
identityId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
),
identity: IdentitiesSchema.pick({ name: true, id: true }).extend({
authMethods: z.array(z.string())
}),
project: SanitizedProjectSchema.pick({ name: true, id: true })
})
.array(),
totalCount: z.number()
})
}
},
handler: async (req) => {
const { identityMemberships, totalCount } = await server.services.identityProject.listProjectIdentities({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
limit: req.query.limit,
offset: req.query.offset,
orderBy: req.query.orderBy,
orderDirection: req.query.orderDirection,
search: req.query.search
});
return { identityMemberships, totalCount };
}
});
server.route({
method: "GET",
url: "/:projectId/identity-memberships/:identityId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectIdentities],
description: "Return project identity membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.projectId),
identityId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.identityId)
}),
response: {
200: z.object({
identityMembership: z.object({
id: z.string(),
identityId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
),
identity: IdentitiesSchema.pick({ name: true, id: true }).extend({
authMethods: z.array(z.string())
}),
project: SanitizedProjectSchema.pick({ name: true, id: true })
})
})
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.getProjectIdentityByIdentityId({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
identityId: req.params.identityId
});
return { identityMembership };
}
});
server.route({
method: "GET",
url: "/identity-memberships/:identityMembershipId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.ProjectIdentities],
params: z.object({
identityMembershipId: z.string().trim()
}),
response: {
200: z.object({
identityMembership: z.object({
id: z.string(),
identityId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
),
identity: IdentitiesSchema.pick({ name: true, id: true }).extend({
authMethods: z.array(z.string())
}),
project: SanitizedProjectSchema.pick({ name: true, id: true })
})
})
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.getProjectIdentityByMembershipId({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityMembershipId: req.params.identityMembershipId
});
return { identityMembership };
}
});
};

View File

@@ -7,7 +7,7 @@ import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => {
export const registerDepreciatedProjectMembershipRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/:projectId/memberships",
@@ -71,7 +71,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
projectId: req.params.projectId,
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_WORKSPACE_MEMBER,
type: EventType.ADD_BATCH_PROJECT_MEMBER,
metadata: memberships.map(({ userId, id }) => ({
userId: userId || "",
membershipId: id,
@@ -141,7 +141,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
...req.auditLogInfo,
projectId: req.params.projectId,
event: {
type: EventType.REMOVE_WORKSPACE_MEMBER,
type: EventType.REMOVE_PROJECT_MEMBER,
metadata: {
userId: membership.userId,
email: ""

View File

@@ -35,7 +35,8 @@ const projectWithEnv = SanitizedProjectSchema.extend({
kmsSecretManagerKeyId: z.string().nullable().optional()
});
export const registerProjectRouter = async (server: FastifyZodProvider) => {
export const registerDepreciatedProjectRouter = async (server: FastifyZodProvider) => {
// depreciated
/* Get project key */
server.route({
method: "GET",
@@ -72,7 +73,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.GET_WORKSPACE_KEY,
type: EventType.GET_PROJECT_KEY,
metadata: {
keyId: key?.id as string
}
@@ -83,68 +84,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
});
/* Start upgrade of a project */
server.route({
method: "POST",
url: "/:projectId/upgrade",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
}),
body: z.object({
userPrivateKey: z.string().trim()
}),
response: {
200: z.void()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.project.upgradeProject({
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
projectId: req.params.projectId,
userPrivateKey: req.body.userPrivateKey
});
}
});
/* Get upgrade status of project */
server.route({
url: "/:projectId/upgrade/status",
method: "GET",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
status: z.string().nullable()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const status = await server.services.project.getProjectUpgradeStatus({
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
actor: req.permission.type,
actorId: req.permission.id
});
return { status };
}
});
/* Create new project */
server.route({
method: "POST",
@@ -224,6 +163,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
});
/* Delete a project by slug */
// moved to DELETE /v1/projects/slug/:slug
server.route({
method: "DELETE",
url: "/:slug",
@@ -276,6 +216,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
});
/* Get a project by slug */
// moved to GET /v1/projects/slug/:slug
server.route({
method: "GET",
url: "/:slug",
@@ -337,7 +278,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
200: SanitizedProjectSchema
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const project = await server.services.project.updateProject({

View File

@@ -1,13 +1,13 @@
import { registerCaRouter } from "./certificate-authority-router";
import { registerGroupProjectRouter } from "./group-project-router";
import { registerDepreciatedGroupProjectRouter } from "./depreciated-group-project-router";
import { registerIdentityOrgRouter } from "./identity-org-router";
import { registerIdentityProjectRouter } from "./identity-project-router";
import { registerDepreciatedIdentityProjectRouter } from "./depreciated-identity-project-router";
import { registerMfaRouter } from "./mfa-router";
import { registerOrgRouter } from "./organization-router";
import { registerPasswordRouter } from "./password-router";
import { registerPkiTemplatesRouter } from "./pki-templates-router";
import { registerProjectMembershipRouter } from "./project-membership-router";
import { registerProjectRouter } from "./project-router";
import { registerDepreciatedProjectMembershipRouter } from "./depreciated-project-membership-router";
import { registerDepreciatedProjectRouter } from "./depreciated-project-router";
import { registerServiceTokenRouter } from "./service-token-router";
import { registerUserRouter } from "./user-router";
@@ -32,12 +32,14 @@ export const registerV2Routes = async (server: FastifyZodProvider) => {
},
{ prefix: "/organizations" }
);
// moved to v1/projects
await server.register(
async (projectServer) => {
await projectServer.register(registerProjectRouter);
await projectServer.register(registerIdentityProjectRouter);
await projectServer.register(registerGroupProjectRouter);
await projectServer.register(registerProjectMembershipRouter);
await projectServer.register(registerDepreciatedProjectRouter);
await projectServer.register(registerDepreciatedIdentityProjectRouter);
await projectServer.register(registerDepreciatedGroupProjectRouter);
await projectServer.register(registerDepreciatedProjectMembershipRouter);
},
{ prefix: "/workspace" }
);

View File

@@ -1,6 +1,5 @@
import { registerExternalMigrationRouter } from "./external-migration-router";
import { registerLoginRouter } from "./login-router";
import { registerSecretBlindIndexRouter } from "./secret-blind-index-router";
import { registerSecretRouter } from "./secret-router";
import { registerSignupRouter } from "./signup-router";
import { registerUserRouter } from "./user-router";
@@ -10,6 +9,5 @@ export const registerV3Routes = async (server: FastifyZodProvider) => {
await server.register(registerLoginRouter, { prefix: "/auth" });
await server.register(registerUserRouter, { prefix: "/users" });
await server.register(registerSecretRouter, { prefix: "/secrets" });
await server.register(registerSecretBlindIndexRouter, { prefix: "/workspaces" });
await server.register(registerExternalMigrationRouter, { prefix: "/external-migration" });
};

View File

@@ -1,109 +0,0 @@
import { z } from "zod";
import { SecretsSchema } from "@app/db/schemas";
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";
export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:projectId/secrets/blind-index-status",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.boolean()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const count = await server.services.secretBlindIndex.getSecretBlindIndexStatus({
projectId: req.params.projectId,
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId
});
return count === 0;
}
});
server.route({
method: "GET",
url: "/:projectId/secrets",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
secrets: SecretsSchema.omit({ secretBlindIndex: true })
.merge(
z.object({
environment: z.string(),
workspace: z.string()
})
)
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const secrets = await server.services.secretBlindIndex.getProjectSecrets({
projectId: req.params.projectId,
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId
});
return { secrets };
}
});
server.route({
method: "POST",
url: "/:projectId/secrets/names",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
projectId: z.string().trim()
}),
body: z.object({
secretsToUpdate: z
.object({
secretName: z.string().trim(),
secretId: z.string().trim()
})
.array()
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.secretBlindIndex.updateProjectSecretName({
projectId: req.params.projectId,
secretsToUpdate: req.body.secretsToUpdate,
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId
});
return { message: "Successfully named workspace secrets" };
}
});
};

View File

@@ -7,10 +7,10 @@ import { TReactQueryOptions } from "@app/types/reactQuery";
import { Actor, AuditLog, TGetAuditLogsFilter } from "./types";
export const auditLogKeys = {
getAuditLogs: (workspaceId: string | null, filters: TGetAuditLogsFilter) =>
[{ workspaceId, filters }, "audit-logs"] as const,
getAuditLogActorFilterOpts: (workspaceId: string) =>
[{ workspaceId }, "audit-log-actor-filters"] as const
getAuditLogs: (projectId: string | null, filters: TGetAuditLogsFilter) =>
[{ projectId, filters }, "audit-logs"] as const,
getAuditLogActorFilterOpts: (projectId: string) =>
[{ projectId }, "audit-log-actor-filters"] as const
};
export const useGetAuditLogs = (
@@ -56,12 +56,12 @@ export const useGetAuditLogs = (
});
};
export const useGetAuditLogActorFilterOpts = (workspaceId: string) => {
export const useGetAuditLogActorFilterOpts = (projectId: string) => {
return useQuery({
queryKey: auditLogKeys.getAuditLogActorFilterOpts(workspaceId),
queryKey: auditLogKeys.getAuditLogActorFilterOpts(projectId),
queryFn: async () => {
const { data } = await apiRequest.get<{ actors: Actor[] }>(
`/api/v1/workspace/${workspaceId}/audit-logs/filters/actors`
`/api/v1/projects/${projectId}/audit-logs/filters/actors`
);
return data.actors;
}

View File

@@ -138,7 +138,7 @@ export const useListProjectGroupUsers = ({
});
const { data } = await apiRequest.get<{ users: TGroupUser[]; totalCount: number }>(
`/api/v2/workspace/${projectId}/groups/${id}/users`,
`/api/v1/projects/${projectId}/groups/${id}/users`,
{
params
}

View File

@@ -4,23 +4,23 @@ import { apiRequest } from "@app/config/request";
import { CreateTagDTO, DeleteTagDTO, UserWsTags, WsTag } from "./types";
const workspaceTags = {
getWsTags: (workspaceID: string) => ["workspace-tags", { workspaceID }] as const
const projectTags = {
getWsTags: (projectID: string) => ["project-tags", { projectID }] as const
};
const fetchWsTag = async (workspaceID: string) => {
const { data } = await apiRequest.get<{ workspaceTags: UserWsTags }>(
`/api/v1/workspace/${workspaceID}/tags`
const fetchWsTag = async (projectID: string) => {
const { data } = await apiRequest.get<{ projectTags: UserWsTags }>(
`/api/v1/projects/${projectID}/tags`
);
return data.workspaceTags;
return data.projectTags;
};
export const useGetWsTags = (workspaceID: string) => {
export const useGetWsTags = (projectID: string) => {
return useQuery({
queryKey: workspaceTags.getWsTags(workspaceID),
queryFn: () => fetchWsTag(workspaceID),
enabled: Boolean(workspaceID)
queryKey: projectTags.getWsTags(projectID),
queryFn: () => fetchWsTag(projectID),
enabled: Boolean(projectID)
});
};
@@ -28,18 +28,18 @@ export const useCreateWsTag = () => {
const queryClient = useQueryClient();
return useMutation<WsTag, object, CreateTagDTO>({
mutationFn: async ({ workspaceID, tagColor, tagSlug }) => {
const { data } = await apiRequest.post<{ workspaceTag: WsTag }>(
`/api/v1/workspace/${workspaceID}/tags`,
mutationFn: async ({ projectID, tagColor, tagSlug }) => {
const { data } = await apiRequest.post<{ projectTag: WsTag }>(
`/api/v1/projects/${projectID}/tags`,
{
color: tagColor || "",
slug: tagSlug
}
);
return data.workspaceTag;
return data.projectTag;
},
onSuccess: (tagData) => {
queryClient.invalidateQueries({ queryKey: workspaceTags.getWsTags(tagData?.projectId) });
queryClient.invalidateQueries({ queryKey: projectTags.getWsTags(tagData?.projectId) });
}
});
};
@@ -49,13 +49,13 @@ export const useDeleteWsTag = () => {
return useMutation<WsTag, object, DeleteTagDTO>({
mutationFn: async ({ tagID, projectId }) => {
const { data } = await apiRequest.delete<{ workspaceTag: WsTag }>(
`/api/v1/workspace/${projectId}/tags/${tagID}`
const { data } = await apiRequest.delete<{ projectTag: WsTag }>(
`/api/v1/projects/${projectId}/tags/${tagID}`
);
return data.workspaceTag;
return data.projectTag;
},
onSuccess: (tagData) => {
queryClient.invalidateQueries({ queryKey: workspaceTags.getWsTags(tagData?.projectId) });
queryClient.invalidateQueries({ queryKey: projectTags.getWsTags(tagData?.projectId) });
}
});
};

View File

@@ -13,7 +13,7 @@ export type WsTag = {
export type WorkspaceTag = { id: string; name: string; slug: string };
export type CreateTagDTO = {
workspaceID: string;
projectID: string;
tagSlug: string;
tagColor: string;
};

View File

@@ -118,13 +118,13 @@ export const useUpdateProjectWorkflowIntegrationConfig = () => {
return useMutation({
mutationFn: async (dto: TUpdateProjectWorkflowIntegrationConfigDTO) => {
const { data } = await apiRequest.put(
`/api/v1/workspace/${dto.workspaceId}/workflow-integration`,
`/api/v1/projects/${dto.projectId}/workflow-integration`,
dto
);
return data;
},
onSuccess: (_, { workspaceId, integration }) => {
onSuccess: (_, { projectId: workspaceId, integration }) => {
queryClient.invalidateQueries({
queryKey: projectKeys.getWorkspaceWorkflowIntegrationConfig(workspaceId, integration)
});
@@ -138,7 +138,7 @@ export const useDeleteProjectWorkflowIntegration = () => {
return useMutation({
mutationFn: async (dto: TDeleteProjectWorkflowIntegrationDTO) => {
const { data } = await apiRequest.delete(
`/api/v1/workspace/${dto.projectId}/workflow-integration/${dto.integration}/${dto.integrationId}`
`/api/v1/projects/${dto.projectId}/workflow-integration/${dto.integration}/${dto.integrationId}`
);
return data;

View File

@@ -106,7 +106,7 @@ export type ProjectWorkflowIntegrationConfig =
export type TUpdateProjectWorkflowIntegrationConfigDTO =
| {
integration: WorkflowIntegrationPlatform.SLACK;
workspaceId: string;
projectId: string;
integrationId: string;
isAccessRequestNotificationEnabled: boolean;
accessRequestChannels: string;
@@ -115,7 +115,7 @@ export type TUpdateProjectWorkflowIntegrationConfigDTO =
}
| {
integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS;
workspaceId: string;
projectId: string;
integrationId: string;
isAccessRequestNotificationEnabled: boolean;
isSecretRequestNotificationEnabled: boolean;

View File

@@ -25,7 +25,6 @@ export {
useGetWorkspaceIdentityMemberships,
useGetWorkspaceIndexStatus,
useGetWorkspaceIntegrations,
useGetWorkspaceSecrets,
useGetWorkspaceUserDetails,
useGetWorkspaceUsers,
useGetWorkspaceWorkflowIntegrationConfig,
@@ -41,7 +40,6 @@ export {
useListWorkspaceSshCertificateTemplates,
useListWorkspaceSshHostGroups,
useListWorkspaceSshHosts,
useNameWorkspaceSecrets,
useSearchProjects,
useToggleAutoCapitalization,
useUpdateIdentityWorkspaceRole,

View File

@@ -15,7 +15,6 @@ import { TIntegration } from "../integrations/types";
import { TPkiAlert } from "../pkiAlerts/types";
import { TPkiCollection } from "../pkiCollections/types";
import { TPkiSubscriber } from "../pkiSubscriber/types";
import { EncryptedSecret } from "../secrets/types";
import { TSshCertificate, TSshCertificateAuthority } from "../sshCa/types";
import { TSshCertificateTemplate } from "../sshCertificateTemplates/types";
import { TSshHost } from "../sshHost/types";
@@ -32,7 +31,6 @@ import {
CreateWorkspaceDTO,
DeleteEnvironmentDTO,
DeleteWorkspaceDTO,
NameWorkspaceSecretsDTO,
ProjectIdentityOrderBy,
ProjectType,
TGetUpgradeProjectStatusDTO,
@@ -73,14 +71,6 @@ const fetchProjectUpgradeStatus = async (projectId: string) => {
return data;
};
export const fetchWorkspaceSecrets = async (projectId: string) => {
const {
data: { secrets }
} = await apiRequest.get<{ secrets: EncryptedSecret[] }>(`/api/v3/projects/${projectId}/secrets`);
return secrets;
};
export const useUpgradeProject = () => {
const queryClient = useQueryClient();
@@ -129,14 +119,6 @@ export const useGetWorkspaceIndexStatus = (projectId: string) => {
});
};
export const useGetWorkspaceSecrets = (projectId: string) => {
return useQuery({
queryKey: projectKeys.getWorkspaceSecrets(projectId),
queryFn: () => fetchWorkspaceSecrets(projectId),
enabled: true
});
};
export const useGetWorkspaceById = (
projectId: string,
dto?: { refetchInterval?: number | false }
@@ -192,22 +174,6 @@ export const useGetUserWorkspaceMemberships = (orgId: string) =>
enabled: Boolean(orgId)
});
export const useNameWorkspaceSecrets = () => {
const queryClient = useQueryClient();
return useMutation<object, object, NameWorkspaceSecretsDTO>({
mutationFn: async ({ projectId, secretsToUpdate }) =>
apiRequest.post(`/api/v3/projects/${projectId}/secrets/names`, {
secretsToUpdate
}),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: projectKeys.getWorkspaceIndexStatus(variables.projectId)
});
}
});
};
const fetchWorkspaceAuthorization = async (projectId: string) => {
const { data } = await apiRequest.get<{ authorizations: IntegrationAuth[] }>(
`/api/v1/projects/${projectId}/authorizations`

View File

@@ -136,7 +136,7 @@ export const MicrosoftTeamsIntegrationForm = ({ onClose }: Props) => {
}
await updateProjectMicrosoftTeamsConfig({
workspaceId: currentWorkspace.id,
projectId: currentWorkspace.id,
isAccessRequestNotificationEnabled: data.isAccessRequestNotificationEnabled,
isSecretRequestNotificationEnabled: data.isSecretRequestNotificationEnabled,
...(data.isAccessRequestNotificationEnabled && {

View File

@@ -83,7 +83,7 @@ export const SlackIntegrationForm = ({ onClose }: Props) => {
await updateProjectSlackConfig({
...data,
workspaceId: currentWorkspace.id,
projectId: currentWorkspace.id,
integration: WorkflowIntegrationPlatform.SLACK,
integrationId: data.slackIntegrationId,
accessRequestChannels: data.accessRequestChannels.filter(Boolean).join(", "),