Merge pull request #225 from Infisical/expand-open-api

Add new organization endpoints to API reference
This commit is contained in:
BlackMagiq
2023-01-16 00:04:08 +07:00
committed by GitHub
28 changed files with 1546 additions and 199 deletions

View File

@ -2258,6 +2258,368 @@
]
}
},
"/api/v2/users/me/organizations": {
"get": {
"summary": "Return organizations that current user is part of",
"description": "Return organizations that current user is part of",
"parameters": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"organizations": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Organization"
},
"description": "Organizations that user is part of"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
}
},
"security": [
{
"apiKeyAuth": []
}
]
}
},
"/api/v2/organizations/{organizationId}/memberships": {
"get": {
"summary": "Return organization memberships",
"description": "Return organization memberships",
"parameters": [
{
"name": "organizationId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of organization"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"memberships": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MembershipOrg"
},
"description": "Memberships of organization"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
}
},
"security": [
{
"apiKeyAuth": []
}
]
}
},
"/api/v2/organizations/{organizationId}/memberships/{membershipId}": {
"patch": {
"summary": "Update organization membership",
"description": "Update organization membership",
"parameters": [
{
"name": "organizationId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of organization"
},
{
"name": "membershipId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of organization membership to update"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"membership": {
"$ref": "#/components/schemas/MembershipOrg",
"description": "Updated organization membership"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
}
},
"security": [
{
"apiKeyAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "Role of organization membership - either owner, admin, or member"
}
}
}
}
}
}
},
"delete": {
"summary": "Delete organization membership",
"description": "Delete organization membership",
"parameters": [
{
"name": "organizationId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of organization"
},
{
"name": "membershipId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of organization membership to delete"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"membership": {
"$ref": "#/components/schemas/MembershipOrg",
"description": "Deleted organization membership"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
}
},
"security": [
{
"apiKeyAuth": []
}
]
}
},
"/api/v2/organizations/{organizationId}/workspaces": {
"get": {
"summary": "Return projects in organization that user is part of",
"description": "Return projects in organization that user is part of",
"parameters": [
{
"name": "organizationId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of organization"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"workspaces": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Project"
},
"description": "Projects of organization"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
}
},
"security": [
{
"apiKeyAuth": []
}
]
}
},
"/api/v2/workspace/{workspaceId}/environments": {
"post": {
"description": "",
"parameters": [
{
"name": "workspaceId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "Bad Request"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"environmentName": {
"example": "any"
},
"environmentSlug": {
"example": "any"
}
}
}
}
}
}
},
"put": {
"description": "",
"parameters": [
{
"name": "workspaceId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "Bad Request"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"environmentName": {
"example": "any"
},
"environmentSlug": {
"example": "any"
},
"oldEnvironmentSlug": {
"example": "any"
}
}
}
}
}
}
},
"delete": {
"description": "",
"parameters": [
{
"name": "workspaceId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "Bad Request"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"environmentSlug": {
"example": "any"
}
}
}
}
}
}
}
},
"/api/v2/workspace/{workspaceId}/secrets": {
"post": {
"description": "",
@ -2450,56 +2812,6 @@
}
},
"/api/v2/workspace/{workspaceId}/memberships/{membershipId}": {
"delete": {
"summary": "Delete project membership",
"description": "Delete project membership",
"parameters": [
{
"name": "workspaceId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of project"
},
{
"name": "membershipId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of membership"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"membership": {
"$ref": "#/components/schemas/Membership",
"description": "Deleted membership"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
}
},
"security": [
{
"apiKeyAuth": []
}
]
},
"patch": {
"summary": "Update project membership",
"description": "Update project membership",
@ -2520,7 +2832,7 @@
"schema": {
"type": "string"
},
"description": "ID of membership"
"description": "ID of project membership to update"
}
],
"responses": {
@ -2565,6 +2877,56 @@
}
}
}
},
"delete": {
"summary": "Delete project membership",
"description": "Delete project membership",
"parameters": [
{
"name": "workspaceId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of project"
},
{
"name": "membershipId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID of project membership to delete"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"membership": {
"$ref": "#/components/schemas/Membership",
"description": "Deleted membership"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
}
},
"security": [
{
"apiKeyAuth": []
}
]
}
},
"/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}": {
@ -3131,7 +3493,7 @@
}
}
},
"/api/v2/api-key-data/": {
"/api/v2/api-key/": {
"get": {
"description": "",
"parameters": [],
@ -3174,7 +3536,7 @@
}
}
},
"/api/v2/api-key-data/{apiKeyDataId}": {
"/api/v2/api-key/{apiKeyDataId}": {
"delete": {
"description": "",
"parameters": [
@ -3302,6 +3664,102 @@
}
}
},
"MembershipOrg": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"_id": {
"type": "string",
"example": ""
},
"email": {
"type": "string",
"example": ""
},
"firstName": {
"type": "string",
"example": ""
},
"lastName": {
"type": "string",
"example": ""
},
"publicKey": {
"type": "string",
"example": ""
},
"updatedAt": {
"type": "string",
"example": ""
},
"createdAt": {
"type": "string",
"example": ""
}
}
},
"organization": {
"type": "string",
"example": ""
},
"role": {
"type": "string",
"example": "owner"
},
"status": {
"type": "string",
"example": "accepted"
}
}
},
"Organization": {
"type": "object",
"properties": {
"_id": {
"type": "string",
"example": ""
},
"name": {
"type": "string",
"example": ""
},
"customerId": {
"type": "string",
"example": ""
}
}
},
"Project": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "My Project"
},
"organization": {
"type": "string",
"example": ""
},
"environments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "development"
},
"slug": {
"type": "string",
"example": "dev"
}
}
}
}
}
},
"ProjectKey": {
"type": "object",
"properties": {

View File

@ -42,9 +42,10 @@ import {
} from './routes/v1';
import {
users as v2UsersRouter,
organizations as v2OrganizationsRouter,
workspace as v2WorkspaceRouter,
secret as v2SecretRouter, // begin to phase out
secrets as v2SecretsRouter,
workspace as v2WorkspaceRouter,
serviceTokenData as v2ServiceTokenDataRouter,
apiKeyData as v2APIKeyDataRouter,
environment as v2EnvironmentRouter,
@ -98,7 +99,7 @@ app.use('/api/v1/membership', v1MembershipRouter);
app.use('/api/v1/key', v1KeyRouter);
app.use('/api/v1/invite-org', v1InviteOrgRouter);
app.use('/api/v1/secret', v1SecretRouter);
app.use('/api/v1/service-token', v1ServiceTokenRouter); // stop supporting
app.use('/api/v1/service-token', v1ServiceTokenRouter); // deprecated
app.use('/api/v1/password', v1PasswordRouter);
app.use('/api/v1/stripe', v1StripeRouter);
app.use('/api/v1/integration', v1IntegrationRouter);
@ -106,9 +107,10 @@ app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
// v2 routes
app.use('/api/v2/users', v2UsersRouter);
app.use('/api/v2/organizations', v2OrganizationsRouter);
app.use('/api/v2/workspace', v2EnvironmentRouter);
app.use('/api/v2/workspace', v2WorkspaceRouter); // TODO: turn into plural route
app.use('/api/v2/secret', v2SecretRouter); // stop supporting, TODO: revise
app.use('/api/v2/workspace', v2WorkspaceRouter);
app.use('/api/v2/secret', v2SecretRouter); // deprecated
app.use('/api/v2/secrets', v2SecretsRouter);
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
app.use('/api/v2/api-key', v2APIKeyDataRouter);
@ -128,7 +130,6 @@ app.use((req, res, next) => {
//* Error Handling Middleware (must be after all routing logic)
app.use(requestErrorHandler)
export const server = app.listen(PORT, () => {
getLogger("backend-main").info(`Server started listening at port ${PORT}`)
});

View File

@ -29,12 +29,6 @@ const productToPriceMap = {
cardAuth: STRIPE_PRODUCT_CARD_AUTH
};
/**
* Return organizations that user is part of
* @param req
* @param res
* @returns
*/
export const getOrganizations = async (req: Request, res: Response) => {
let organizations;
try {

View File

@ -1,4 +1,5 @@
import * as usersController from './usersController';
import * as organizationsController from './organizationsController';
import * as workspaceController from './workspaceController';
import * as serviceTokenDataController from './serviceTokenDataController';
import * as apiKeyDataController from './apiKeyDataController';
@ -8,6 +9,7 @@ import * as environmentController from './environmentController';
export {
usersController,
organizationsController,
workspaceController,
serviceTokenDataController,
apiKeyDataController,

View File

@ -0,0 +1,296 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import {
MembershipOrg,
Membership,
Workspace
} from '../../models';
import { deleteMembershipOrg } from '../../helpers/membershipOrg';
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
/**
* Return memberships for organization with id [organizationId]
* @param req
* @param res
*/
export const getOrganizationMemberships = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Return organization memberships'
#swagger.description = 'Return organization memberships'
#swagger.security = [{
"apiKeyAuth": []
}]
#swagger.parameters['organizationId'] = {
"description": "ID of organization",
"required": true,
"type": "string"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"memberships": {
"type": "array",
"items": {
$ref: "#/components/schemas/MembershipOrg"
},
"description": "Memberships of organization"
}
}
}
}
}
}
*/
let memberships;
try {
const { organizationId } = req.params;
memberships = await MembershipOrg.find({
organization: organizationId
}).populate('user', '+publicKey');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get organization memberships'
});
}
return res.status(200).send({
memberships
});
}
/**
* Update role of membership with id [membershipId] to role [role]
* @param req
* @param res
*/
export const updateOrganizationMembership = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Update organization membership'
#swagger.description = 'Update organization membership'
#swagger.security = [{
"apiKeyAuth": []
}]
#swagger.parameters['organizationId'] = {
"description": "ID of organization",
"required": true,
"type": "string"
}
#swagger.parameters['membershipId'] = {
"description": "ID of organization membership to update",
"required": true,
"type": "string"
}
#swagger.requestBody = {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"role": {
"type": "string",
"description": "Role of organization membership - either owner, admin, or member",
}
}
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"membership": {
$ref: "#/components/schemas/MembershipOrg",
"description": "Updated organization membership"
}
}
}
}
}
}
*/
let membership;
try {
const { membershipId } = req.params;
const { role } = req.body;
membership = await MembershipOrg.findByIdAndUpdate(
membershipId,
{
role
}, {
new: true
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to update organization membership'
});
}
return res.status(200).send({
membership
});
}
/**
* Delete organization membership with id [membershipId]
* @param req
* @param res
* @returns
*/
export const deleteOrganizationMembership = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Delete organization membership'
#swagger.description = 'Delete organization membership'
#swagger.security = [{
"apiKeyAuth": []
}]
#swagger.parameters['organizationId'] = {
"description": "ID of organization",
"required": true,
"type": "string"
}
#swagger.parameters['membershipId'] = {
"description": "ID of organization membership to delete",
"required": true,
"type": "string"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"membership": {
$ref: "#/components/schemas/MembershipOrg",
"description": "Deleted organization membership"
}
}
}
}
}
}
*/
let membership;
try {
const { membershipId } = req.params;
// delete organization membership
membership = await deleteMembershipOrg({
membershipOrgId: membershipId
});
await updateSubscriptionOrgQuantity({
organizationId: membership.organization.toString()
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete organization membership'
});
}
return res.status(200).send({
membership
});
}
/**
* Return workspaces for organization with id [organizationId] that user has
* access to
* @param req
* @param res
*/
export const getOrganizationWorkspaces = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Return projects in organization that user is part of'
#swagger.description = 'Return projects in organization that user is part of'
#swagger.security = [{
"apiKeyAuth": []
}]
#swagger.parameters['organizationId'] = {
"description": "ID of organization",
"required": true,
"type": "string"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"workspaces": {
"type": "array",
"items": {
$ref: "#/components/schemas/Project"
},
"description": "Projects of organization"
}
}
}
}
}
}
*/
let workspaces;
try {
const { organizationId } = req.params;
const workspacesSet = new Set(
(
await Workspace.find(
{
organization: organizationId
},
'_id'
)
).map((w) => w._id.toString())
);
workspaces = (
await Membership.find({
user: req.user._id
}).populate('workspace')
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get organization workspaces'
});
}
return res.status(200).send({
workspaces
});
}

View File

@ -1,9 +1,16 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import {
User
User,
MembershipOrg
} from '../../models';
/**
* Return the current user.
* @param req
* @param res
* @returns
*/
export const getMe = async (req: Request, res: Response) => {
/*
#swagger.summary = "Retrieve the current user on the request"
@ -39,11 +46,64 @@ export const getMe = async (req: Request, res: Response) => {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get user'
message: 'Failed to get current user'
});
}
return res.status(200).send({
user
});
}
/**
* Return organizations that the current user is part of.
* @param req
* @param res
*/
export const getMyOrganizations = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Return organizations that current user is part of'
#swagger.description = 'Return organizations that current user is part of'
#swagger.security = [{
"apiKeyAuth": []
}]
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"organizations": {
"type": "array",
"items": {
$ref: "#/components/schemas/Organization"
},
"description": "Organizations that user is part of"
}
}
}
}
}
}
*/
let organizations;
try {
organizations = (
await MembershipOrg.find({
user: req.user._id
}).populate('organization')
).map((m) => m.organization);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get current user's organizations"
});
}
return res.status(200).send({
organizations
});
}

View File

@ -311,76 +311,6 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
});
}
/**
* Delete workspace membership with id [membershipId]
* @param req
* @param res
* @returns
*/
export const deleteWorkspaceMembership = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Delete project membership'
#swagger.description = 'Delete project membership'
#swagger.security = [{
"apiKeyAuth": []
}]
#swagger.parameters['workspaceId'] = {
"description": "ID of project",
"required": true,
"type": "string"
}
#swagger.parameters['membershipId'] = {
"description": "ID of membership",
"required": true,
"type": "string"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"membership": {
$ref: "#/components/schemas/Membership",
"description": "Deleted membership"
}
}
}
}
}
}
*/
let membership;
try {
const {
membershipId
} = req.params;
membership = await Membership.findByIdAndDelete(membershipId);
if (!membership) throw new Error('Failed to delete workspace membership');
await Key.deleteMany({
receiver: membership.user,
workspace: membership.workspace
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete workspace membership'
});
}
return res.status(200).send({
membership
});
}
/**
* Update role of membership with id [membershipId] to role [role]
* @param req
@ -403,7 +333,7 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
}
#swagger.parameters['membershipId'] = {
"description": "ID of membership",
"description": "ID of project membership to update",
"required": true,
"type": "string"
}
@ -467,4 +397,74 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
return res.status(200).send({
membership
});
}
/**
* Delete workspace membership with id [membershipId]
* @param req
* @param res
* @returns
*/
export const deleteWorkspaceMembership = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Delete project membership'
#swagger.description = 'Delete project membership'
#swagger.security = [{
"apiKeyAuth": []
}]
#swagger.parameters['workspaceId'] = {
"description": "ID of project",
"required": true,
"type": "string"
}
#swagger.parameters['membershipId'] = {
"description": "ID of project membership to delete",
"required": true,
"type": "string"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"membership": {
$ref: "#/components/schemas/Membership",
"description": "Deleted membership"
}
}
}
}
}
}
*/
let membership;
try {
const {
membershipId
} = req.params;
membership = await Membership.findByIdAndDelete(membershipId);
if (!membership) throw new Error('Failed to delete workspace membership');
await Key.deleteMany({
receiver: membership.user,
workspace: membership.workspace
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete workspace membership'
});
}
return res.status(200).send({
membership
});
}

View File

@ -7,6 +7,7 @@ import { Membership, Key } from '../models';
* @param {Object} obj
* @param {String} obj.userId - id of user to validate
* @param {String} obj.workspaceId - id of workspace
* @returns {Membership} membership - membership of user with id [userId] for workspace with id [workspaceId]
*/
const validateMembership = async ({
userId,

View File

@ -1,6 +1,42 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { MembershipOrg, Workspace, Membership, Key } from '../models';
/**
* Validate that user with id [userId] is a member of organization with id [organizationId]
* and has at least one of the roles in [acceptedRoles]
*
*/
const validateMembership = async ({
userId,
organizationId,
acceptedRoles
}: {
userId: string;
organizationId: string;
acceptedRoles: string[];
}) => {
let membership;
try {
membership = await MembershipOrg.findOne({
user: new Types.ObjectId(userId),
organization: new Types.ObjectId(organizationId)
});
if (!membership) throw new Error('Failed to find organization membership');
if (!acceptedRoles.includes(membership.role)) {
throw new Error('Failed to validate organization membership role');
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to validate organization membership');
}
return membership;
}
/**
* Return organization membership matching criteria specified in
* query [queryObj]
@ -84,6 +120,8 @@ const deleteMembershipOrg = async ({
_id: membershipOrgId
});
if (!deletedMembershipOrg) throw new Error('Failed to delete organization membership');
// delete keys associated with organization membership
if (deletedMembershipOrg?.user) {
// case: organization membership had a registered user
@ -117,4 +155,9 @@ const deleteMembershipOrg = async ({
return deletedMembershipOrg;
};
export { findMembershipOrg, addMembershipsOrg, deleteMembershipOrg };
export {
validateMembership,
findMembershipOrg,
addMembershipsOrg,
deleteMembershipOrg
};

View File

@ -3,6 +3,7 @@ import requireBotAuth from './requireBotAuth';
import requireSignupAuth from './requireSignupAuth';
import requireWorkspaceAuth from './requireWorkspaceAuth';
import requireMembershipAuth from './requireMembershipAuth';
import requireMembershipOrgAuth from './requireMembershipOrgAuth';
import requireOrganizationAuth from './requireOrganizationAuth';
import requireIntegrationAuth from './requireIntegrationAuth';
import requireIntegrationAuthorizationAuth from './requireIntegrationAuthorizationAuth';
@ -18,6 +19,7 @@ export {
requireSignupAuth,
requireWorkspaceAuth,
requireMembershipAuth,
requireMembershipOrgAuth,
requireOrganizationAuth,
requireIntegrationAuth,
requireIntegrationAuthorizationAuth,

View File

@ -6,11 +6,12 @@ import {
import { validateMembership } from '../helpers/membership';
type req = 'params' | 'body' | 'query';
/**
* Validate membership with id [membershipId] and that user with id
* [req.user._id] can modify that membership.
* @param {Object} obj
* @param {String[]} obj.acceptedRoles - accepted workspace roles for JWT auth
* @param {String[]} obj.acceptedRoles - accepted workspace roles
* @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing
*/
const requireMembershipAuth = ({

View File

@ -0,0 +1,49 @@
import { Request, Response, NextFunction } from 'express';
import { UnauthorizedRequestError } from '../utils/errors';
import {
MembershipOrg
} from '../models';
import { validateMembership } from '../helpers/membershipOrg';
type req = 'params' | 'body' | 'query';
/**
* Validate (organization) membership id [membershipId] and that user with id
* [req.user._id] can modify that membership.
* @param {Object} obj
* @param {String[]} obj.acceptedRoles - accepted organization roles
* @param {String[]} obj.location - location of [membershipId] on request (e.g. params, body) for parsing
*/
const requireMembershipOrgAuth = ({
acceptedRoles,
location = 'params'
}: {
acceptedRoles: string[];
location?: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const { membershipId } = req[location];
const membershipOrg = await MembershipOrg.findById(membershipId);
if (!membershipOrg) throw new Error('Failed to find target organization membership');
const targetMembership = await validateMembership({
userId: req.user._id.toString(),
organizationId: membershipOrg.organization.toString(),
acceptedRoles
});
req.targetMembership = targetMembership;
return next();
} catch (err) {
return next(UnauthorizedRequestError({
message: 'Unable to validate organization membership'
}));
}
}
}
export default requireMembershipOrgAuth;

View File

@ -4,6 +4,8 @@ import { body, param } from 'express-validator';
import { requireAuth, validateRequest } from '../../middleware';
import { membershipController } from '../../controllers/v1';
// note: ALL DEPRECIATED (moved to api/v2/workspace/:workspaceId/memberships/:membershipId)
router.get( // used for old CLI (deprecate)
'/:workspaceId/connect',
requireAuth({

View File

@ -9,7 +9,7 @@ import {
import { OWNER, ADMIN, MEMBER, ACCEPTED } from '../../variables';
import { organizationController } from '../../controllers/v1';
router.get(
router.get( // deprecated (moved to api/v2/users/me/organizations)
'/',
requireAuth({
acceptedAuthModes: ['jwt']
@ -41,7 +41,7 @@ router.get(
organizationController.getOrganization
);
router.get(
router.get( // deprecated (moved to api/v2/organizations/:organizationId/memberships)
'/:organizationId/users',
requireAuth({
acceptedAuthModes: ['jwt']
@ -56,7 +56,7 @@ router.get(
);
router.get(
'/:organizationId/my-workspaces',
'/:organizationId/my-workspaces', // deprecated (moved to api/v2/organizations/:organizationId/workspaces)
requireAuth({
acceptedAuthModes: ['jwt']
}),

View File

@ -1,16 +1,18 @@
import users from './users';
import secret from './secret'; // stop-supporting
import secrets from './secrets';
import organizations from './organizations';
import workspace from './workspace';
import secret from './secret'; // deprecated
import secrets from './secrets';
import serviceTokenData from './serviceTokenData';
import apiKeyData from './apiKeyData';
import environment from "./environment"
export {
users,
organizations,
workspace,
secret,
secrets,
workspace,
serviceTokenData,
apiKeyData,
environment

View File

@ -0,0 +1,80 @@
import express from 'express';
const router = express.Router();
import {
requireAuth,
requireOrganizationAuth,
requireMembershipOrgAuth,
validateRequest
} from '../../middleware';
import { body, param, query } from 'express-validator';
import { OWNER, ADMIN, MEMBER, ACCEPTED } from '../../variables';
import { organizationsController } from '../../controllers/v2';
// TODO: /POST to create membership
router.get(
'/:organizationId/memberships',
param('organizationId').exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED]
}),
organizationsController.getOrganizationMemberships
);
router.patch(
'/:organizationId/memberships/:membershipId',
param('organizationId').exists().trim(),
param('membershipId').exists().trim(),
body('role').exists().isString().trim().isIn([ADMIN, MEMBER]),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED]
}),
requireMembershipOrgAuth({
acceptedRoles: [OWNER, ADMIN]
}),
organizationsController.updateOrganizationMembership
);
router.delete(
'/:organizationId/memberships/:membershipId',
param('organizationId').exists().trim(),
param('membershipId').exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED]
}),
requireMembershipOrgAuth({
acceptedRoles: [OWNER, ADMIN]
}),
organizationsController.deleteOrganizationMembership
);
router.get(
'/:organizationId/workspaces',
param('organizationId').exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED]
}),
organizationsController.getOrganizationWorkspaces
);
export default router;

View File

@ -10,7 +10,7 @@ import { ADMIN, MEMBER } from '../../variables';
import { CreateSecretRequestBody, ModifySecretRequestBody } from '../../types/secret';
import { secretController } from '../../controllers/v2';
// note to devs: stop supporting
// note to devs: stop supporting these routes [deprecated]
const router = express.Router();

View File

@ -13,4 +13,12 @@ router.get(
usersController.getMe
);
router.get(
'/me/organizations',
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
usersController.getMyOrganizations
);
export default router;

View File

@ -83,23 +83,6 @@ router.get( // new - TODO: rewire dashboard to this route
workspaceController.getWorkspaceMemberships
);
router.delete( // TODO - rewire dashboard to this route
'/:workspaceId/memberships/:membershipId',
param('workspaceId').exists().trim(),
param('membershipId').exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
}),
requireMembershipAuth({
acceptedRoles: [ADMIN]
}),
workspaceController.deleteWorkspaceMembership
);
router.patch( // TODO - rewire dashboard to this route
'/:workspaceId/memberships/:membershipId',
param('workspaceId').exists().trim(),
@ -118,4 +101,21 @@ router.patch( // TODO - rewire dashboard to this route
workspaceController.updateWorkspaceMembership
);
router.delete( // TODO - rewire dashboard to this route
'/:workspaceId/memberships/:membershipId',
param('workspaceId').exists().trim(),
param('membershipId').exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN],
}),
requireMembershipAuth({
acceptedRoles: [ADMIN]
}),
workspaceController.deleteWorkspaceMembership
);
export default router;

View File

@ -66,6 +66,33 @@ const generateOpenAPISpec = async () => {
workspace: '',
role: 'admin'
},
MembershipOrg: {
user: {
_id: '',
email: '',
firstName: '',
lastName: '',
publicKey: '',
updatedAt: '',
createdAt: ''
},
organization: '',
role: 'owner',
status: 'accepted'
},
Organization: {
_id: '',
name: '',
customerId: ''
},
Project: {
name: 'My Project',
organization: '',
environments: [{
name: 'development',
slug: 'dev'
}]
},
ProjectKey: {
encryptedkey: '',
nonce: '',

View File

@ -0,0 +1,4 @@
---
title: "Delete Membership"
openapi: "DELETE /api/v2/organizations/{organizationId}/memberships/{membershipId}"
---

View File

@ -0,0 +1,4 @@
---
title: "Get Memberships"
openapi: "GET /api/v2/organizations/{organizationId}/memberships"
---

View File

@ -0,0 +1,4 @@
---
title: "Update Membership"
openapi: "PATCH /api/v2/organizations/{organizationId}/memberships/{membershipId}"
---

View File

@ -0,0 +1,4 @@
---
title: "Get Projects"
openapi: "GET /api/v2/organizations/{organizationId}/workspaces"
---

View File

@ -1,4 +1,4 @@
---
title: "Get Current User"
title: "Get My User"
openapi: "GET /api/v2/users/me"
---

View File

@ -0,0 +1,4 @@
---
title: "Get My Organizations"
openapi: "GET /api/v2/users/me/organizations"
---

View File

@ -162,7 +162,17 @@
{
"group": "Users",
"pages": [
"api-reference/endpoints/users/me"
"api-reference/endpoints/users/me",
"api-reference/endpoints/users/my-organizations"
]
},
{
"group": "Organizations",
"pages": [
"api-reference/endpoints/organizations/memberships",
"api-reference/endpoints/organizations/update-membership",
"api-reference/endpoints/organizations/delete-membership",
"api-reference/endpoints/organizations/workspaces"
]
},
{

View File

@ -1392,6 +1392,229 @@ paths:
description: Bad Request
security:
- apiKeyAuth: []
/api/v2/users/me/organizations:
get:
summary: Return organizations that current user is part of
description: Return organizations that current user is part of
parameters: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
organizations:
type: array
items:
$ref: '#/components/schemas/Organization'
description: Organizations that user is part of
'400':
description: Bad Request
security:
- apiKeyAuth: []
/api/v2/organizations/{organizationId}/memberships:
get:
summary: Return organization memberships
description: Return organization memberships
parameters:
- name: organizationId
in: path
required: true
schema:
type: string
description: ID of organization
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
memberships:
type: array
items:
$ref: '#/components/schemas/MembershipOrg'
description: Memberships of organization
'400':
description: Bad Request
security:
- apiKeyAuth: []
/api/v2/organizations/{organizationId}/memberships/{membershipId}:
patch:
summary: Update organization membership
description: Update organization membership
parameters:
- name: organizationId
in: path
required: true
schema:
type: string
description: ID of organization
- name: membershipId
in: path
required: true
schema:
type: string
description: ID of organization membership to update
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
membership:
$ref: '#/components/schemas/MembershipOrg'
description: Updated organization membership
'400':
description: Bad Request
security:
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
role:
type: string
description: >-
Role of organization membership - either owner, admin, or
member
delete:
summary: Delete organization membership
description: Delete organization membership
parameters:
- name: organizationId
in: path
required: true
schema:
type: string
description: ID of organization
- name: membershipId
in: path
required: true
schema:
type: string
description: ID of organization membership to delete
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
membership:
$ref: '#/components/schemas/MembershipOrg'
description: Deleted organization membership
'400':
description: Bad Request
security:
- apiKeyAuth: []
/api/v2/organizations/{organizationId}/workspaces:
get:
summary: Return projects in organization that user is part of
description: Return projects in organization that user is part of
parameters:
- name: organizationId
in: path
required: true
schema:
type: string
description: ID of organization
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
workspaces:
type: array
items:
$ref: '#/components/schemas/Project'
description: Projects of organization
'400':
description: Bad Request
security:
- apiKeyAuth: []
/api/v2/workspace/{workspaceId}/environments:
post:
description: ''
parameters:
- name: workspaceId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
'400':
description: Bad Request
requestBody:
content:
application/json:
schema:
type: object
properties:
environmentName:
example: any
environmentSlug:
example: any
put:
description: ''
parameters:
- name: workspaceId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
'400':
description: Bad Request
requestBody:
content:
application/json:
schema:
type: object
properties:
environmentName:
example: any
environmentSlug:
example: any
oldEnvironmentSlug:
example: any
delete:
description: ''
parameters:
- name: workspaceId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
'400':
description: Bad Request
requestBody:
content:
application/json:
schema:
type: object
properties:
environmentSlug:
example: any
/api/v2/workspace/{workspaceId}/secrets:
post:
description: ''
@ -1509,37 +1732,6 @@ paths:
security:
- apiKeyAuth: []
/api/v2/workspace/{workspaceId}/memberships/{membershipId}:
delete:
summary: Delete project membership
description: Delete project membership
parameters:
- name: workspaceId
in: path
required: true
schema:
type: string
description: ID of project
- name: membershipId
in: path
required: true
schema:
type: string
description: ID of membership
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
membership:
$ref: '#/components/schemas/Membership'
description: Deleted membership
'400':
description: Bad Request
security:
- apiKeyAuth: []
patch:
summary: Update project membership
description: Update project membership
@ -1555,7 +1747,7 @@ paths:
required: true
schema:
type: string
description: ID of membership
description: ID of project membership to update
responses:
'200':
description: OK
@ -1581,6 +1773,37 @@ paths:
role:
type: string
description: Role of membership - either admin or member
delete:
summary: Delete project membership
description: Delete project membership
parameters:
- name: workspaceId
in: path
required: true
schema:
type: string
description: ID of project
- name: membershipId
in: path
required: true
schema:
type: string
description: ID of project membership to delete
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
membership:
$ref: '#/components/schemas/Membership'
description: Deleted membership
'400':
description: Bad Request
security:
- apiKeyAuth: []
/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}:
post:
description: ''
@ -1927,7 +2150,7 @@ paths:
description: OK
'400':
description: Bad Request
/api/v2/api-key-data/:
/api/v2/api-key/:
get:
description: ''
parameters: []
@ -1954,7 +2177,7 @@ paths:
example: any
expiresIn:
example: any
/api/v2/api-key-data/{apiKeyDataId}:
/api/v2/api-key/{apiKeyDataId}:
delete:
description: ''
parameters:
@ -2043,6 +2266,74 @@ components:
role:
type: string
example: admin
MembershipOrg:
type: object
properties:
user:
type: object
properties:
_id:
type: string
example: ''
email:
type: string
example: ''
firstName:
type: string
example: ''
lastName:
type: string
example: ''
publicKey:
type: string
example: ''
updatedAt:
type: string
example: ''
createdAt:
type: string
example: ''
organization:
type: string
example: ''
role:
type: string
example: owner
status:
type: string
example: accepted
Organization:
type: object
properties:
_id:
type: string
example: ''
name:
type: string
example: ''
customerId:
type: string
example: ''
Project:
type: object
properties:
name:
type: string
example: My Project
organization:
type: string
example: ''
environments:
type: array
items:
type: object
properties:
name:
type: string
example: development
slug:
type: string
example: dev
ProjectKey:
type: object
properties: