mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 02:28:39 +00:00
Merge branch 'Infisical:main' into feat/multi-profile
This commit is contained in:
+4
-4
@@ -31,12 +31,12 @@ MONGO_PASSWORD=example
|
|||||||
SITE_URL=http://localhost:8080
|
SITE_URL=http://localhost:8080
|
||||||
|
|
||||||
# Mail/SMTP
|
# Mail/SMTP
|
||||||
SMTP_HOST= # required
|
SMTP_HOST=
|
||||||
SMTP_USERNAME= # required
|
SMTP_USERNAME=
|
||||||
SMTP_PASSWORD= # required
|
SMTP_PASSWORD=
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
SMTP_SECURE=false
|
SMTP_SECURE=false
|
||||||
SMTP_FROM_ADDRESS= # required
|
SMTP_FROM_ADDRESS=
|
||||||
SMTP_FROM_NAME=Infisical
|
SMTP_FROM_NAME=Infisical
|
||||||
|
|
||||||
# Integration
|
# Integration
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: ☁️ Checkout source
|
- name: ☁️ Checkout source
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
- name: 📦 Install dependencies to test all dependencies
|
||||||
|
run: npm ci --only-production
|
||||||
|
working-directory: backend
|
||||||
|
- name: 🧪 Run tests
|
||||||
|
run: npm run test:ci
|
||||||
|
working-directory: backend
|
||||||
- name: Save commit hashes for tag
|
- name: Save commit hashes for tag
|
||||||
id: commit
|
id: commit
|
||||||
uses: pr-mpt/actions-commit-hash@v2
|
uses: pr-mpt/actions-commit-hash@v2
|
||||||
@@ -46,7 +52,7 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
context: backend
|
context: backend
|
||||||
tags: infisical/backend:${{ steps.commit.outputs.short }},
|
tags: infisical/backend:${{ steps.commit.outputs.short }},
|
||||||
infisical/backend:latest
|
infisical/backend:latest
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
||||||
frontend-image:
|
frontend-image:
|
||||||
@@ -95,7 +101,7 @@ jobs:
|
|||||||
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
||||||
context: frontend
|
context: frontend
|
||||||
tags: infisical/frontend:${{ steps.commit.outputs.short }},
|
tags: infisical/frontend:${{ steps.commit.outputs.short }},
|
||||||
infisical/frontend:latest
|
infisical/frontend:latest
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
build-args: |
|
build-args: |
|
||||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||||
|
|||||||
Generated
+1693
-534
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-secrets-manager": "^3.281.0",
|
"@aws-sdk/client-secrets-manager": "^3.294.0",
|
||||||
"@godaddy/terminus": "^4.11.2",
|
"@godaddy/terminus": "^4.11.2",
|
||||||
"@octokit/rest": "^19.0.5",
|
"@octokit/rest": "^19.0.5",
|
||||||
"@sentry/tracing": "^7.39.0",
|
"@sentry/tracing": "^7.41.0",
|
||||||
"@sentry/node": "^7.40.0",
|
"@sentry/node": "^7.40.0",
|
||||||
|
"@sentry/node": "^7.41.0",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/libsodium-wrappers": "^0.7.10",
|
"@types/libsodium-wrappers": "^0.7.10",
|
||||||
"await-to-js": "^3.0.0",
|
"await-to-js": "^3.0.0",
|
||||||
"aws-sdk": "^2.1324.0",
|
"aws-sdk": "^2.1338.0",
|
||||||
"axios": "^1.1.3",
|
"axios": "^1.1.3",
|
||||||
"axios-retry": "^3.4.0",
|
"axios-retry": "^3.4.0",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
@@ -29,9 +30,9 @@
|
|||||||
"jsrp": "^0.2.4",
|
"jsrp": "^0.2.4",
|
||||||
"libsodium-wrappers": "^0.7.10",
|
"libsodium-wrappers": "^0.7.10",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mongoose": "^6.10.2",
|
"mongoose": "^6.10.3",
|
||||||
"nodemailer": "^6.8.0",
|
"nodemailer": "^6.8.0",
|
||||||
"posthog-node": "^2.5.4",
|
"posthog-node": "^2.6.0",
|
||||||
"query-string": "^7.1.3",
|
"query-string": "^7.1.3",
|
||||||
"request-ip": "^3.3.0",
|
"request-ip": "^3.3.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
@@ -57,7 +58,7 @@
|
|||||||
"lint-and-fix": "eslint . --ext .ts --fix",
|
"lint-and-fix": "eslint . --ext .ts --fix",
|
||||||
"lint-staged": "lint-staged",
|
"lint-staged": "lint-staged",
|
||||||
"pretest": "docker compose -f test-resources/docker-compose.test.yml up -d",
|
"pretest": "docker compose -f test-resources/docker-compose.test.yml up -d",
|
||||||
"test": "cross-env NODE_ENV=test jest --verbose --testTimeout=10000 --detectOpenHandles",
|
"test": "cross-env NODE_ENV=test jest --verbose --testTimeout=10000 --detectOpenHandles; npm run posttest",
|
||||||
"test:ci": "npm test -- --watchAll=false --ci --reporters=default --reporters=jest-junit --reporters=github-actions --coverage --testLocationInResults --json --outputFile=coverage/report.json",
|
"test:ci": "npm test -- --watchAll=false --ci --reporters=default --reporters=jest-junit --reporters=github-actions --coverage --testLocationInResults --json --outputFile=coverage/report.json",
|
||||||
"posttest": "docker compose -f test-resources/docker-compose.test.yml down"
|
"posttest": "docker compose -f test-resources/docker-compose.test.yml down"
|
||||||
},
|
},
|
||||||
@@ -80,7 +81,7 @@
|
|||||||
"@types/cookie-parser": "^1.4.3",
|
"@types/cookie-parser": "^1.4.3",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
"@types/express": "^4.17.14",
|
"@types/express": "^4.17.14",
|
||||||
"@types/jest": "^29.2.4",
|
"@types/jest": "^29.5.0",
|
||||||
"@types/jsonwebtoken": "^8.5.9",
|
"@types/jsonwebtoken": "^8.5.9",
|
||||||
"@types/lodash": "^4.14.191",
|
"@types/lodash": "^4.14.191",
|
||||||
"@types/node": "^18.11.3",
|
"@types/node": "^18.11.3",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const getJwtServiceSecret = () => infisical.get('JWT_SERVICE_SECRET')!;
|
|||||||
export const getJwtSignupLifetime = () => infisical.get('JWT_SIGNUP_LIFETIME')! || '15m';
|
export const getJwtSignupLifetime = () => infisical.get('JWT_SIGNUP_LIFETIME')! || '15m';
|
||||||
export const getJwtSignupSecret = () => infisical.get('JWT_SIGNUP_SECRET')!;
|
export const getJwtSignupSecret = () => infisical.get('JWT_SIGNUP_SECRET')!;
|
||||||
export const getMongoURL = () => infisical.get('MONGO_URL')!;
|
export const getMongoURL = () => infisical.get('MONGO_URL')!;
|
||||||
export const getNodeEnv = () => infisical.get('NODE_ENV')!;
|
export const getNodeEnv = () => infisical.get('NODE_ENV')! || 'production';
|
||||||
export const getVerboseErrorOutput = () => infisical.get('VERBOSE_ERROR_OUTPUT')! === 'true' && true;
|
export const getVerboseErrorOutput = () => infisical.get('VERBOSE_ERROR_OUTPUT')! === 'true' && true;
|
||||||
export const getLokiHost = () => infisical.get('LOKI_HOST')!;
|
export const getLokiHost = () => infisical.get('LOKI_HOST')!;
|
||||||
export const getClientIdAzure = () => infisical.get('CLIENT_ID_AZURE')!;
|
export const getClientIdAzure = () => infisical.get('CLIENT_ID_AZURE')!;
|
||||||
@@ -49,3 +49,16 @@ export const getStripeWebhookSecret = () => infisical.get('STRIPE_WEBHOOK_SECRET
|
|||||||
export const getTelemetryEnabled = () => infisical.get('TELEMETRY_ENABLED')! !== 'false' && true;
|
export const getTelemetryEnabled = () => infisical.get('TELEMETRY_ENABLED')! !== 'false' && true;
|
||||||
export const getLoopsApiKey = () => infisical.get('LOOPS_API_KEY')!;
|
export const getLoopsApiKey = () => infisical.get('LOOPS_API_KEY')!;
|
||||||
export const getSmtpConfigured = () => infisical.get('SMTP_HOST') == '' || infisical.get('SMTP_HOST') == undefined ? false : true
|
export const getSmtpConfigured = () => infisical.get('SMTP_HOST') == '' || infisical.get('SMTP_HOST') == undefined ? false : true
|
||||||
|
export const getHttpsEnabled = () => {
|
||||||
|
if (getNodeEnv() != "production") {
|
||||||
|
// no https for anything other than prod
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (infisical.get('HTTPS_ENABLED') == undefined || infisical.get('HTTPS_ENABLED') == "") {
|
||||||
|
// default when no value present
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return infisical.get('HTTPS_ENABLED') === 'true' && true
|
||||||
|
}
|
||||||
@@ -15,10 +15,10 @@ import { BadRequestError } from '../../utils/errors';
|
|||||||
import { EELogService } from '../../ee/services';
|
import { EELogService } from '../../ee/services';
|
||||||
import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this
|
import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this
|
||||||
import {
|
import {
|
||||||
getNodeEnv,
|
|
||||||
getJwtRefreshSecret,
|
getJwtRefreshSecret,
|
||||||
getJwtAuthLifetime,
|
getJwtAuthLifetime,
|
||||||
getJwtAuthSecret
|
getJwtAuthSecret,
|
||||||
|
getHttpsEnabled
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
|
|
||||||
declare module 'jsonwebtoken' {
|
declare module 'jsonwebtoken' {
|
||||||
@@ -126,7 +126,7 @@ export const login2 = async (req: Request, res: Response) => {
|
|||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
secure: getNodeEnv() === 'production' ? true : false
|
secure: getHttpsEnabled()
|
||||||
});
|
});
|
||||||
|
|
||||||
const loginAction = await EELogService.createAction({
|
const loginAction = await EELogService.createAction({
|
||||||
@@ -182,7 +182,7 @@ export const logout = async (req: Request, res: Response) => {
|
|||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
secure: getNodeEnv() === 'production' ? true : false
|
secure: getHttpsEnabled() as boolean
|
||||||
});
|
});
|
||||||
|
|
||||||
const logoutAction = await EELogService.createAction({
|
const logoutAction = await EELogService.createAction({
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ import {
|
|||||||
getTeams,
|
getTeams,
|
||||||
revokeAccess
|
revokeAccess
|
||||||
} from '../../integrations';
|
} from '../../integrations';
|
||||||
|
import {
|
||||||
|
INTEGRATION_VERCEL_API_URL,
|
||||||
|
INTEGRATION_RAILWAY_API_URL
|
||||||
|
} from '../../variables';
|
||||||
|
import request from '../../config/request';
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Return integration authorization with id [integrationAuthId]
|
* Return integration authorization with id [integrationAuthId]
|
||||||
@@ -188,22 +193,203 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
|
export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
|
||||||
let teams;
|
const teams = await getTeams({
|
||||||
try {
|
integrationAuth: req.integrationAuth,
|
||||||
teams = await getTeams({
|
accessToken: req.accessToken
|
||||||
integrationAuth: req.integrationAuth,
|
});
|
||||||
accessToken: req.accessToken
|
|
||||||
|
return res.status(200).send({
|
||||||
|
teams
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of available Vercel (preview) branches for Vercel project with
|
||||||
|
* id [appId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const getIntegrationAuthVercelBranches = async (req: Request, res: Response) => {
|
||||||
|
const { integrationAuthId } = req.params;
|
||||||
|
const appId = req.query.appId as string;
|
||||||
|
|
||||||
|
interface VercelBranch {
|
||||||
|
ref: string;
|
||||||
|
lastCommit: string;
|
||||||
|
isProtected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
projectId: appId,
|
||||||
|
...(req.integrationAuth.teamId ? {
|
||||||
|
teamId: req.integrationAuth.teamId
|
||||||
|
} : {})
|
||||||
|
});
|
||||||
|
|
||||||
|
let branches: string[] = [];
|
||||||
|
|
||||||
|
if (appId && appId !== '') {
|
||||||
|
const { data }: { data: VercelBranch[] } = await request.get(
|
||||||
|
`${INTEGRATION_VERCEL_API_URL}/v1/integrations/git-branches`,
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${req.accessToken}`,
|
||||||
|
'Accept-Encoding': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
branches = data.map((b) => b.ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
branches
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of Railway environments for Railway project with
|
||||||
|
* id [appId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: Response) => {
|
||||||
|
const { integrationAuthId } = req.params;
|
||||||
|
const appId = req.query.appId as string;
|
||||||
|
|
||||||
|
interface RailwayEnvironment {
|
||||||
|
node: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
isEphemeral: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Environment {
|
||||||
|
environmentId: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let environments: Environment[] = [];
|
||||||
|
|
||||||
|
if (appId && appId !== '') {
|
||||||
|
const query = `
|
||||||
|
query GetEnvironments($projectId: String!, $after: String, $before: String, $first: Int, $isEphemeral: Boolean, $last: Int) {
|
||||||
|
environments(projectId: $projectId, after: $after, before: $before, first: $first, isEphemeral: $isEphemeral, last: $last) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
isEphemeral
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const variables = {
|
||||||
|
projectId: appId
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: { data: { environments: { edges } } } } = await request.post(INTEGRATION_RAILWAY_API_URL, {
|
||||||
|
query,
|
||||||
|
variables,
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${req.accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
environments = edges.map((e: RailwayEnvironment) => {
|
||||||
Sentry.captureException(err);
|
return ({
|
||||||
return res.status(400).send({
|
name: e.node.name,
|
||||||
message: "Failed to get integration authorization teams"
|
environmentId: e.node.id
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
teams
|
environments
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of Railway services for Railway project with id
|
||||||
|
* [appId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const getIntegrationAuthRailwayServices = async (req: Request, res: Response) => {
|
||||||
|
const { integrationAuthId } = req.params;
|
||||||
|
const appId = req.query.appId as string;
|
||||||
|
|
||||||
|
interface RailwayService {
|
||||||
|
node: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Service {
|
||||||
|
name: string;
|
||||||
|
serviceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let services: Service[] = [];
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
query project($id: String!) {
|
||||||
|
project(id: $id) {
|
||||||
|
createdAt
|
||||||
|
deletedAt
|
||||||
|
id
|
||||||
|
description
|
||||||
|
expiredAt
|
||||||
|
isPublic
|
||||||
|
isTempProject
|
||||||
|
isUpdatable
|
||||||
|
name
|
||||||
|
prDeploys
|
||||||
|
teamId
|
||||||
|
updatedAt
|
||||||
|
upstreamUrl
|
||||||
|
services {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (appId && appId !== '') {
|
||||||
|
const variables = {
|
||||||
|
id: appId
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: { data: { project: { services: { edges } } } } } = await request.post(INTEGRATION_RAILWAY_API_URL, {
|
||||||
|
query,
|
||||||
|
variables
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${req.accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
services = edges.map((e: RailwayService) => ({
|
||||||
|
name: e.node.name,
|
||||||
|
serviceId: e.node.id
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
services
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ export const createIntegration = async (req: Request, res: Response) => {
|
|||||||
isActive,
|
isActive,
|
||||||
sourceEnvironment,
|
sourceEnvironment,
|
||||||
targetEnvironment,
|
targetEnvironment,
|
||||||
|
targetEnvironmentId,
|
||||||
|
targetService,
|
||||||
|
targetServiceId,
|
||||||
owner,
|
owner,
|
||||||
path,
|
path,
|
||||||
region
|
region
|
||||||
@@ -39,12 +42,15 @@ export const createIntegration = async (req: Request, res: Response) => {
|
|||||||
app,
|
app,
|
||||||
appId,
|
appId,
|
||||||
targetEnvironment,
|
targetEnvironment,
|
||||||
|
targetEnvironmentId,
|
||||||
|
targetService,
|
||||||
|
targetServiceId,
|
||||||
owner,
|
owner,
|
||||||
path,
|
path,
|
||||||
region,
|
region,
|
||||||
integration: req.integrationAuth.integration,
|
integration: req.integrationAuth.integration,
|
||||||
integrationAuth: new Types.ObjectId(integrationAuthId)
|
integrationAuth: new Types.ObjectId(integrationAuthId)
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
if (integration) {
|
if (integration) {
|
||||||
// trigger event - push secrets
|
// trigger event - push secrets
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
import { pushKeys } from '../../helpers/key';
|
import { pushKeys } from '../../helpers/key';
|
||||||
import { eventPushSecrets } from '../../events';
|
import { eventPushSecrets } from '../../events';
|
||||||
import { EventService } from '../../services';
|
import { EventService } from '../../services';
|
||||||
import { getPostHogClient } from '../../services';
|
import { TelemetryService } from '../../services';
|
||||||
|
|
||||||
interface PushSecret {
|
interface PushSecret {
|
||||||
ciphertextKey: string;
|
ciphertextKey: string;
|
||||||
@@ -38,7 +38,7 @@ export const pushSecrets = async (req: Request, res: Response) => {
|
|||||||
// upload (encrypted) secrets to workspace with id [workspaceId]
|
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
let { secrets }: { secrets: PushSecret[] } = req.body;
|
let { secrets }: { secrets: PushSecret[] } = req.body;
|
||||||
const { keys, environment, channel } = req.body;
|
const { keys, environment, channel } = req.body;
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
@@ -112,7 +112,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
|||||||
let secrets;
|
let secrets;
|
||||||
let key;
|
let key;
|
||||||
try {
|
try {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const environment: string = req.query.environment as string;
|
const environment: string = req.query.environment as string;
|
||||||
const channel: string = req.query.channel as string;
|
const channel: string = req.query.channel as string;
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
@@ -181,7 +181,7 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
|
|||||||
let secrets;
|
let secrets;
|
||||||
let key;
|
let key;
|
||||||
try {
|
try {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const environment: string = req.query.environment as string;
|
const environment: string = req.query.environment as string;
|
||||||
const channel: string = req.query.channel as string;
|
const channel: string = req.query.channel as string;
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export const createAPIKeyData = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
apiKeyData = await new APIKeyData({
|
apiKeyData = await new APIKeyData({
|
||||||
name,
|
name,
|
||||||
|
lastUsed: new Date(),
|
||||||
expiresAt,
|
expiresAt,
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
secretHash
|
secretHash
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ import {
|
|||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this
|
import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this
|
||||||
import {
|
import {
|
||||||
getNodeEnv,
|
|
||||||
getJwtMfaLifetime,
|
getJwtMfaLifetime,
|
||||||
getJwtMfaSecret
|
getJwtMfaSecret,
|
||||||
|
getHttpsEnabled
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
|
|
||||||
declare module 'jsonwebtoken' {
|
declare module 'jsonwebtoken' {
|
||||||
@@ -163,7 +163,7 @@ export const login2 = async (req: Request, res: Response) => {
|
|||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
secure: getNodeEnv() === 'production' ? true : false
|
secure: getHttpsEnabled()
|
||||||
});
|
});
|
||||||
|
|
||||||
// case: user does not have MFA enablgged
|
// case: user does not have MFA enablgged
|
||||||
@@ -302,7 +302,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
|
|||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
secure: getNodeEnv() === 'production' ? true : false
|
secure: getHttpsEnabled()
|
||||||
});
|
});
|
||||||
|
|
||||||
interface VerifyMfaTokenRes {
|
interface VerifyMfaTokenRes {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
import { SecretVersion } from '../../ee/models';
|
import { SecretVersion } from '../../ee/models';
|
||||||
import { BadRequestError } from '../../utils/errors';
|
import { BadRequestError } from '../../utils/errors';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization';
|
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new workspace environment named [environmentName] under workspace with id
|
* Create new workspace environment named [environmentName] under workspace with id
|
||||||
@@ -244,8 +244,8 @@ export const getAllAccessibleEnvironmentsOfWorkspace = async (
|
|||||||
throw BadRequestError()
|
throw BadRequestError()
|
||||||
}
|
}
|
||||||
relatedWorkspace.environments.forEach(environment => {
|
relatedWorkspace.environments.forEach(environment => {
|
||||||
const isReadBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: ABILITY_READ })
|
const isReadBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: PERMISSION_READ_SECRETS })
|
||||||
const isWriteBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: ABILITY_WRITE })
|
const isWriteBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: PERMISSION_WRITE_SECRETS })
|
||||||
if (isReadBlocked && isWriteBlocked) {
|
if (isReadBlocked && isWriteBlocked) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import * as serviceTokenDataController from './serviceTokenDataController';
|
|||||||
import * as apiKeyDataController from './apiKeyDataController';
|
import * as apiKeyDataController from './apiKeyDataController';
|
||||||
import * as secretController from './secretController';
|
import * as secretController from './secretController';
|
||||||
import * as secretsController from './secretsController';
|
import * as secretsController from './secretsController';
|
||||||
|
import * as serviceAccountsController from './serviceAccountsController';
|
||||||
import * as environmentController from './environmentController';
|
import * as environmentController from './environmentController';
|
||||||
import * as tagController from './tagController';
|
import * as tagController from './tagController';
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ export {
|
|||||||
apiKeyDataController,
|
apiKeyDataController,
|
||||||
secretController,
|
secretController,
|
||||||
secretsController,
|
secretsController,
|
||||||
|
serviceAccountsController,
|
||||||
environmentController,
|
environmentController,
|
||||||
tagController
|
tagController
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
MembershipOrg,
|
MembershipOrg,
|
||||||
Membership,
|
Membership,
|
||||||
Workspace
|
Workspace,
|
||||||
|
ServiceAccount
|
||||||
} from '../../models';
|
} from '../../models';
|
||||||
import { deleteMembershipOrg } from '../../helpers/membershipOrg';
|
import { deleteMembershipOrg } from '../../helpers/membershipOrg';
|
||||||
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
|
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
|
||||||
@@ -260,37 +262,45 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let workspaces;
|
const { organizationId } = req.params;
|
||||||
try {
|
|
||||||
const { organizationId } = req.params;
|
|
||||||
|
|
||||||
const workspacesSet = new Set(
|
const workspacesSet = new Set(
|
||||||
(
|
(
|
||||||
await Workspace.find(
|
await Workspace.find(
|
||||||
{
|
{
|
||||||
organization: organizationId
|
organization: organizationId
|
||||||
},
|
},
|
||||||
'_id'
|
'_id'
|
||||||
)
|
)
|
||||||
).map((w) => w._id.toString())
|
).map((w) => w._id.toString())
|
||||||
);
|
);
|
||||||
|
|
||||||
workspaces = (
|
const workspaces = (
|
||||||
await Membership.find({
|
await Membership.find({
|
||||||
user: req.user._id
|
user: req.user._id
|
||||||
}).populate('workspace')
|
}).populate('workspace')
|
||||||
)
|
)
|
||||||
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
|
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
|
||||||
.map((m) => m.workspace);
|
.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({
|
return res.status(200).send({
|
||||||
workspaces
|
workspaces
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return service accounts for organization with id [organizationId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const getOrganizationServiceAccounts = async (req: Request, res: Response) => {
|
||||||
|
const { organizationId } = req.params;
|
||||||
|
|
||||||
|
const serviceAccounts = await ServiceAccount.find({
|
||||||
|
organization: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccounts
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ const { ValidationError } = mongoose.Error;
|
|||||||
import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors';
|
import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors';
|
||||||
import { AnyBulkWriteOperation } from 'mongodb';
|
import { AnyBulkWriteOperation } from 'mongodb';
|
||||||
import { SECRET_PERSONAL, SECRET_SHARED } from "../../variables";
|
import { SECRET_PERSONAL, SECRET_SHARED } from "../../variables";
|
||||||
import { getPostHogClient } from '../../services';
|
import { TelemetryService } from '../../services';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create secret for workspace with id [workspaceId] and environment [environment]
|
* Create secret for workspace with id [workspaceId] and environment [environment]
|
||||||
@@ -15,7 +15,7 @@ import { getPostHogClient } from '../../services';
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const createSecret = async (req: Request, res: Response) => {
|
export const createSecret = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const secretToCreate: CreateSecretRequestBody = req.body.secret;
|
const secretToCreate: CreateSecretRequestBody = req.body.secret;
|
||||||
const { workspaceId, environment } = req.params
|
const { workspaceId, environment } = req.params
|
||||||
const sanitizedSecret: SanitizedSecretForCreate = {
|
const sanitizedSecret: SanitizedSecretForCreate = {
|
||||||
@@ -68,7 +68,7 @@ export const createSecret = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const createSecrets = async (req: Request, res: Response) => {
|
export const createSecrets = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets;
|
const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets;
|
||||||
const { workspaceId, environment } = req.params
|
const { workspaceId, environment } = req.params
|
||||||
const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = []
|
const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = []
|
||||||
@@ -130,7 +130,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const deleteSecrets = async (req: Request, res: Response) => {
|
export const deleteSecrets = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const { workspaceId, environmentName } = req.params
|
const { workspaceId, environmentName } = req.params
|
||||||
const secretIdsToDelete: string[] = req.body.secretIds
|
const secretIdsToDelete: string[] = req.body.secretIds
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const deleteSecret = async (req: Request, res: Response) => {
|
export const deleteSecret = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
await Secret.findByIdAndDelete(req._secret._id)
|
await Secret.findByIdAndDelete(req._secret._id)
|
||||||
|
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
@@ -213,7 +213,7 @@ export const deleteSecret = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const updateSecrets = async (req: Request, res: Response) => {
|
export const updateSecrets = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const { workspaceId, environmentName } = req.params
|
const { workspaceId, environmentName } = req.params
|
||||||
const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets;
|
const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets;
|
||||||
const [secretIdsUserCanModifyError, secretIdsUserCanModify] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then())
|
const [secretIdsUserCanModifyError, secretIdsUserCanModify] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then())
|
||||||
@@ -281,7 +281,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const updateSecret = async (req: Request, res: Response) => {
|
export const updateSecret = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const { workspaceId, environmentName } = req.params
|
const { workspaceId, environmentName } = req.params
|
||||||
const secretModificationsRequested: ModifySecretRequestBody = req.body.secret;
|
const secretModificationsRequested: ModifySecretRequestBody = req.body.secret;
|
||||||
|
|
||||||
@@ -335,7 +335,7 @@ export const updateSecret = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getSecrets = async (req: Request, res: Response) => {
|
export const getSecrets = async (req: Request, res: Response) => {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const { environment } = req.query;
|
const { environment } = req.query;
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import { UnauthorizedRequestError, ValidationError } from '../../utils/errors';
|
|||||||
import { EventService } from '../../services';
|
import { EventService } from '../../services';
|
||||||
import { eventPushSecrets } from '../../events';
|
import { eventPushSecrets } from '../../events';
|
||||||
import { EESecretService, EELogService } from '../../ee/services';
|
import { EESecretService, EELogService } from '../../ee/services';
|
||||||
import { getPostHogClient } from '../../services';
|
import { TelemetryService } from '../../services';
|
||||||
import { getChannelFromUserAgent } from '../../utils/posthog';
|
import { getChannelFromUserAgent } from '../../utils/posthog';
|
||||||
import { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization';
|
import { PERMISSION_WRITE_SECRETS } from '../../variables';
|
||||||
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
|
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
|
||||||
import Tag from '../../models/tag';
|
import Tag from '../../models/tag';
|
||||||
import _ from 'lodash';
|
import _, { eq } from 'lodash';
|
||||||
import {
|
import {
|
||||||
BatchSecretRequest,
|
BatchSecretRequest,
|
||||||
BatchSecret
|
BatchSecret
|
||||||
@@ -28,12 +28,13 @@ import {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Peform a batch of any specified CUD secret operations
|
* Peform a batch of any specified CUD secret operations
|
||||||
|
* (used by dashboard)
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const batchSecrets = async (req: Request, res: Response) => {
|
export const batchSecrets = async (req: Request, res: Response) => {
|
||||||
const channel = getChannelFromUserAgent(req.headers['user-agent']);
|
const channel = getChannelFromUserAgent(req.headers['user-agent']);
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -91,7 +92,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const addAction = await EELogService.createAction({
|
const addAction = await EELogService.createAction({
|
||||||
name: ACTION_ADD_SECRETS,
|
name: ACTION_ADD_SECRETS,
|
||||||
userId: req.user._id,
|
userId: req.user?._id,
|
||||||
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
secretIds: createdSecrets.map((n) => n._id)
|
secretIds: createdSecrets.map((n) => n._id)
|
||||||
}) as IAction;
|
}) as IAction;
|
||||||
@@ -328,14 +331,15 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const postHogClient = getPostHogClient();
|
|
||||||
|
|
||||||
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
||||||
const { workspaceId, environment }: { workspaceId: string, environment: string } = req.body;
|
const { workspaceId, environment }: { workspaceId: string, environment: string } = req.body;
|
||||||
|
|
||||||
const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_WRITE)
|
if (req.user) {
|
||||||
if (!hasAccess) {
|
const hasAccess = await userHasWorkspaceAccess(req.user, new Types.ObjectId(workspaceId), environment, PERMISSION_WRITE_SECRETS)
|
||||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
if (!hasAccess) {
|
||||||
|
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let listOfSecretsToCreate;
|
let listOfSecretsToCreate;
|
||||||
@@ -378,7 +382,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
version: 1,
|
version: 1,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
type,
|
type,
|
||||||
user: type === SECRET_PERSONAL ? req.user : undefined,
|
user: (req.user && type === SECRET_PERSONAL) ? req.user : undefined,
|
||||||
environment,
|
environment,
|
||||||
secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretKeyIV,
|
secretKeyIV,
|
||||||
@@ -391,7 +395,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
secretCommentTag,
|
secretCommentTag,
|
||||||
tags
|
tags
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map((insertedSecret) => insertedSecret.toObject());
|
const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map((insertedSecret) => insertedSecret.toObject());
|
||||||
|
|
||||||
@@ -447,14 +451,18 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const addAction = await EELogService.createAction({
|
const addAction = await EELogService.createAction({
|
||||||
name: ACTION_ADD_SECRETS,
|
name: ACTION_ADD_SECRETS,
|
||||||
userId: req.user._id,
|
userId: req.user?._id,
|
||||||
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
secretIds: newlyCreatedSecrets.map((n) => n._id)
|
secretIds: newlyCreatedSecrets.map((n) => n._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) create (audit) log
|
// (EE) create (audit) log
|
||||||
addAction && await EELogService.createLog({
|
addAction && await EELogService.createLog({
|
||||||
userId: req.user._id.toString(),
|
userId: req.user?._id,
|
||||||
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
actions: [addAction],
|
actions: [addAction],
|
||||||
channel,
|
channel,
|
||||||
@@ -466,10 +474,15 @@ export const createSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId
|
workspaceId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: 'secrets added',
|
event: 'secrets added',
|
||||||
distinctId: req.user.email,
|
distinctId: TelemetryService.getDistinctId({
|
||||||
|
user: req.user,
|
||||||
|
serviceAccount: req.serviceAccount,
|
||||||
|
serviceTokenData: req.serviceTokenData
|
||||||
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: listOfSecretsToCreate.length,
|
numberOfSecrets: listOfSecretsToCreate.length,
|
||||||
environment,
|
environment,
|
||||||
@@ -533,91 +546,120 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const postHogClient = getPostHogClient();
|
const { tagSlugs } = req.query;
|
||||||
|
const workspaceId = req.query.workspaceId as string;
|
||||||
|
const environment = req.query.environment as string;
|
||||||
|
|
||||||
const { workspaceId, environment, tagSlugs } = req.query;
|
// secrets to return
|
||||||
|
let secrets: ISecret[] = [];
|
||||||
|
|
||||||
|
// query tags table to get all tags ids for the tag names for the given workspace
|
||||||
|
let tagIds = [];
|
||||||
const tagNamesList = typeof tagSlugs === 'string' && tagSlugs !== '' ? tagSlugs.split(',') : [];
|
const tagNamesList = typeof tagSlugs === 'string' && tagSlugs !== '' ? tagSlugs.split(',') : [];
|
||||||
let userId = "" // used for getting personal secrets for user
|
|
||||||
let userEmail = "" // used for posthog
|
|
||||||
if (req.user) {
|
|
||||||
userId = req.user._id;
|
|
||||||
userEmail = req.user.email;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.serviceTokenData) {
|
|
||||||
userId = req.serviceTokenData.user._id
|
|
||||||
userEmail = req.serviceTokenData.user.email;
|
|
||||||
}
|
|
||||||
|
|
||||||
// none service token case as service tokens are already scoped to env and project
|
|
||||||
let hasWriteOnlyAccess
|
|
||||||
if (!req.serviceTokenData) {
|
|
||||||
hasWriteOnlyAccess = await userHasWriteOnlyAbility(userId, workspaceId, environment)
|
|
||||||
const hasNoAccess = await userHasNoAbility(userId, workspaceId, environment)
|
|
||||||
if (hasNoAccess) {
|
|
||||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let secrets: any
|
|
||||||
let secretQuery: any
|
|
||||||
|
|
||||||
if (tagNamesList != undefined && tagNamesList.length != 0) {
|
if (tagNamesList != undefined && tagNamesList.length != 0) {
|
||||||
const workspaceFromDB = await Tag.find({ workspace: workspaceId })
|
const workspaceFromDB = await Tag.find({ workspace: workspaceId });
|
||||||
|
tagIds = _.map(tagNamesList, (tagName) => {
|
||||||
const tagIds = _.map(tagNamesList, (tagName) => {
|
|
||||||
const tag = _.find(workspaceFromDB, { slug: tagName });
|
const tag = _.find(workspaceFromDB, { slug: tagName });
|
||||||
return tag ? tag.id : null;
|
return tag ? tag.id : null;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
secretQuery = {
|
if (req.user) {
|
||||||
workspace: workspaceId,
|
// case: client authorization is via JWT
|
||||||
environment,
|
const hasWriteOnlyAccess = await userHasWriteOnlyAbility(req.user._id, new Types.ObjectId(workspaceId), environment)
|
||||||
$or: [
|
const hasNoAccess = await userHasNoAbility(req.user._id, new Types.ObjectId(workspaceId), environment)
|
||||||
{ user: userId },
|
if (hasNoAccess) {
|
||||||
{ user: { $exists: false } }
|
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
||||||
],
|
|
||||||
tags: { $in: tagIds },
|
|
||||||
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
secretQuery = {
|
const secretQuery: any = {
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
environment,
|
environment,
|
||||||
$or: [
|
$or: [
|
||||||
{ user: userId },
|
{ user: req.user._id }, // personal secrets for this user
|
||||||
{ user: { $exists: false } }
|
{ user: { $exists: false } } // shared secrets from workspace
|
||||||
],
|
]
|
||||||
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
|
}
|
||||||
|
|
||||||
|
if (tagIds.length > 0) {
|
||||||
|
secretQuery.tags = { $in: tagIds };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasWriteOnlyAccess) {
|
||||||
|
// only return the secret keys and not the values since user does not have right to see values
|
||||||
|
secrets = await Secret.find(secretQuery).select("secretKeyCiphertext secretKeyIV secretKeyTag").populate("tags")
|
||||||
|
} else {
|
||||||
|
secrets = await Secret.find(secretQuery).populate("tags")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasWriteOnlyAccess) {
|
// case: client authorization is via service token
|
||||||
secrets = await Secret.find(secretQuery).select("secretKeyCiphertext secretKeyIV secretKeyTag")
|
if (req.serviceTokenData) {
|
||||||
} else {
|
const userId = req.serviceTokenData.user._id
|
||||||
secrets = await Secret.find(secretQuery).populate("tags")
|
|
||||||
|
const secretQuery: any = {
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment,
|
||||||
|
$or: [
|
||||||
|
{ user: userId }, // personal secrets for this user
|
||||||
|
{ user: { $exists: false } } // shared secrets from workspace
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagIds.length > 0) {
|
||||||
|
secretQuery.tags = { $in: tagIds };
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check if service token has write only permission
|
||||||
|
|
||||||
|
secrets = await Secret.find(secretQuery).populate("tags");
|
||||||
|
}
|
||||||
|
|
||||||
|
// case: client authorization is via service account
|
||||||
|
if (req.serviceAccount) {
|
||||||
|
const secretQuery: any = {
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment,
|
||||||
|
user: { $exists: false } // shared secrets only from workspace
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagIds.length > 0) {
|
||||||
|
secretQuery.tags = { $in: tagIds };
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets = await Secret.find(secretQuery).populate("tags");
|
||||||
}
|
}
|
||||||
|
|
||||||
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
||||||
|
|
||||||
const readAction = await EELogService.createAction({
|
const readAction = await EELogService.createAction({
|
||||||
name: ACTION_READ_SECRETS,
|
name: ACTION_READ_SECRETS,
|
||||||
userId: new Types.ObjectId(userId),
|
userId: req.user?._id,
|
||||||
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(workspaceId as string),
|
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||||
secretIds: secrets.map((n: any) => n._id)
|
secretIds: secrets.map((n: any) => n._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
readAction && await EELogService.createLog({
|
readAction && await EELogService.createLog({
|
||||||
userId: new Types.ObjectId(userId),
|
userId: req.user?._id,
|
||||||
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(workspaceId as string),
|
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||||
actions: [readAction],
|
actions: [readAction],
|
||||||
channel,
|
channel,
|
||||||
ipAddress: req.ip
|
ipAddress: req.ip
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: 'secrets pulled',
|
event: 'secrets pulled',
|
||||||
distinctId: userEmail,
|
distinctId: TelemetryService.getDistinctId({
|
||||||
|
user: req.user,
|
||||||
|
serviceAccount: req.serviceAccount,
|
||||||
|
serviceTokenData: req.serviceTokenData
|
||||||
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: secrets.length,
|
numberOfSecrets: secrets.length,
|
||||||
environment,
|
environment,
|
||||||
@@ -633,59 +675,6 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getOnlySecretKeys = async (req: Request, res: Response) => {
|
|
||||||
const { workspaceId, environment } = req.query;
|
|
||||||
|
|
||||||
let userId = "" // used for getting personal secrets for user
|
|
||||||
let userEmail = "" // used for posthog
|
|
||||||
if (req.user) {
|
|
||||||
userId = req.user._id;
|
|
||||||
userEmail = req.user.email;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.serviceTokenData) {
|
|
||||||
userId = req.serviceTokenData.user._id
|
|
||||||
userEmail = req.serviceTokenData.user.email;
|
|
||||||
}
|
|
||||||
|
|
||||||
// none service token case as service tokens are already scoped
|
|
||||||
if (!req.serviceTokenData) {
|
|
||||||
const hasAccess = await userHasWorkspaceAccess(userId, workspaceId, environment, ABILITY_READ)
|
|
||||||
if (!hasAccess) {
|
|
||||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [err, secretKeys] = await to(Secret.find(
|
|
||||||
{
|
|
||||||
workspace: workspaceId,
|
|
||||||
environment,
|
|
||||||
$or: [
|
|
||||||
{ user: userId },
|
|
||||||
{ user: { $exists: false } }
|
|
||||||
],
|
|
||||||
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.select("secretKeyIV secretKeyTag secretKeyCiphertext")
|
|
||||||
.then())
|
|
||||||
|
|
||||||
if (err) throw ValidationError({ message: 'Failed to get secrets', stack: err.stack });
|
|
||||||
|
|
||||||
// readAction && await EELogService.createLog({
|
|
||||||
// userId: new Types.ObjectId(userId),
|
|
||||||
// workspaceId: new Types.ObjectId(workspaceId as string),
|
|
||||||
// actions: [readAction],
|
|
||||||
// channel,
|
|
||||||
// ipAddress: req.ip
|
|
||||||
// });
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
secretKeys
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update secret(s)
|
* Update secret(s)
|
||||||
* @param req
|
* @param req
|
||||||
@@ -736,10 +725,8 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const postHogClient = getPostHogClient();
|
|
||||||
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
|
||||||
|
|
||||||
// TODO: move type
|
|
||||||
interface PatchSecret {
|
interface PatchSecret {
|
||||||
id: string;
|
id: string;
|
||||||
secretKeyCiphertext: string;
|
secretKeyCiphertext: string;
|
||||||
@@ -865,14 +852,18 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const updateAction = await EELogService.createAction({
|
const updateAction = await EELogService.createAction({
|
||||||
name: ACTION_UPDATE_SECRETS,
|
name: ACTION_UPDATE_SECRETS,
|
||||||
userId: req.user._id,
|
userId: req.user?._id,
|
||||||
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(key),
|
workspaceId: new Types.ObjectId(key),
|
||||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) create (audit) log
|
// (EE) create (audit) log
|
||||||
updateAction && await EELogService.createLog({
|
updateAction && await EELogService.createLog({
|
||||||
userId: req.user._id.toString(),
|
userId: req.user?._id,
|
||||||
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(key),
|
workspaceId: new Types.ObjectId(key),
|
||||||
actions: [updateAction],
|
actions: [updateAction],
|
||||||
channel,
|
channel,
|
||||||
@@ -884,10 +875,15 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId: key
|
workspaceId: key
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: 'secrets modified',
|
event: 'secrets modified',
|
||||||
distinctId: req.user.email,
|
distinctId: TelemetryService.getDistinctId({
|
||||||
|
user: req.user,
|
||||||
|
serviceAccount: req.serviceAccount,
|
||||||
|
serviceTokenData: req.serviceTokenData
|
||||||
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: workspaceSecretObj[key].length,
|
numberOfSecrets: workspaceSecretObj[key].length,
|
||||||
environment: workspaceSecretObj[key][0].environment,
|
environment: workspaceSecretObj[key][0].environment,
|
||||||
@@ -909,7 +905,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete secret(s) with id [workspaceId] and environment [environment]
|
* Delete secret(s)
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
@@ -958,7 +954,11 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const postHogClient = getPostHogClient();
|
|
||||||
|
return res.status(200).send({
|
||||||
|
message: 'delete secrets!!'
|
||||||
|
});
|
||||||
|
|
||||||
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
||||||
const toDelete = req.secrets.map((s: any) => s._id);
|
const toDelete = req.secrets.map((s: any) => s._id);
|
||||||
|
|
||||||
@@ -992,14 +992,18 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
const deleteAction = await EELogService.createAction({
|
const deleteAction = await EELogService.createAction({
|
||||||
name: ACTION_DELETE_SECRETS,
|
name: ACTION_DELETE_SECRETS,
|
||||||
userId: req.user._id,
|
userId: req.user?._id,
|
||||||
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(key),
|
workspaceId: new Types.ObjectId(key),
|
||||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||||
});
|
});
|
||||||
|
|
||||||
// (EE) create (audit) log
|
// (EE) create (audit) log
|
||||||
deleteAction && await EELogService.createLog({
|
deleteAction && await EELogService.createLog({
|
||||||
userId: req.user._id.toString(),
|
userId: req.user?._id,
|
||||||
|
serviceAccountId: req.serviceAccount?._id,
|
||||||
|
serviceTokenDataId: req.serviceTokenData?._id,
|
||||||
workspaceId: new Types.ObjectId(key),
|
workspaceId: new Types.ObjectId(key),
|
||||||
actions: [deleteAction],
|
actions: [deleteAction],
|
||||||
channel,
|
channel,
|
||||||
@@ -1011,10 +1015,15 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
|||||||
workspaceId: key
|
workspaceId: key
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
if (postHogClient) {
|
if (postHogClient) {
|
||||||
postHogClient.capture({
|
postHogClient.capture({
|
||||||
event: 'secrets deleted',
|
event: 'secrets deleted',
|
||||||
distinctId: req.user.email,
|
distinctId: TelemetryService.getDistinctId({
|
||||||
|
user: req.user,
|
||||||
|
serviceAccount: req.serviceAccount,
|
||||||
|
serviceTokenData: req.serviceTokenData
|
||||||
|
}),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: workspaceSecretObj[key].length,
|
numberOfSecrets: workspaceSecretObj[key].length,
|
||||||
environment: workspaceSecretObj[key][0].environment,
|
environment: workspaceSecretObj[key][0].environment,
|
||||||
|
|||||||
@@ -0,0 +1,306 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import {
|
||||||
|
ServiceAccount,
|
||||||
|
ServiceAccountKey,
|
||||||
|
ServiceAccountOrganizationPermission,
|
||||||
|
ServiceAccountWorkspacePermission
|
||||||
|
} from '../../models';
|
||||||
|
import {
|
||||||
|
CreateServiceAccountDto
|
||||||
|
} from '../../interfaces/serviceAccounts/dto';
|
||||||
|
import { BadRequestError, ServiceAccountNotFoundError } from '../../utils/errors';
|
||||||
|
import { getSaltRounds } from '../../config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return service account tied to the request (service account) client
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const getCurrentServiceAccount = async (req: Request, res: Response) => {
|
||||||
|
const serviceAccount = await ServiceAccount.findById(req.serviceAccount._id);
|
||||||
|
|
||||||
|
if (!serviceAccount) {
|
||||||
|
throw ServiceAccountNotFoundError({ message: 'Failed to find service account' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return service account with id [serviceAccountId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const getServiceAccountById = async (req: Request, res: Response) => {
|
||||||
|
const { serviceAccountId } = req.params;
|
||||||
|
|
||||||
|
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
|
||||||
|
|
||||||
|
if (!serviceAccount) {
|
||||||
|
throw ServiceAccountNotFoundError({ message: 'Failed to find service account' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new service account under organization with id [organizationId]
|
||||||
|
* that has access to workspaces [workspaces]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const createServiceAccount = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
organizationId,
|
||||||
|
publicKey,
|
||||||
|
expiresIn,
|
||||||
|
}: CreateServiceAccountDto = req.body;
|
||||||
|
|
||||||
|
let expiresAt;
|
||||||
|
if (expiresIn) {
|
||||||
|
expiresAt = new Date();
|
||||||
|
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
const secret = crypto.randomBytes(16).toString('base64');
|
||||||
|
const secretHash = await bcrypt.hash(secret, getSaltRounds());
|
||||||
|
|
||||||
|
// create service account
|
||||||
|
const serviceAccount = await new ServiceAccount({
|
||||||
|
name,
|
||||||
|
organization: new Types.ObjectId(organizationId),
|
||||||
|
user: req.user,
|
||||||
|
publicKey,
|
||||||
|
lastUsed: new Date(),
|
||||||
|
expiresAt,
|
||||||
|
secretHash
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
const serviceAccountObj = serviceAccount.toObject();
|
||||||
|
|
||||||
|
delete serviceAccountObj.secretHash;
|
||||||
|
|
||||||
|
// provision default org-level permission for service account
|
||||||
|
await new ServiceAccountOrganizationPermission({
|
||||||
|
serviceAccount: serviceAccount._id
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
const secretId = Buffer.from(serviceAccount._id.toString(), 'hex').toString('base64');
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccountAccessKey: `sa.${secretId}.${secret}`,
|
||||||
|
serviceAccount: serviceAccountObj
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change name of service account with id [serviceAccountId] to [name]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const changeServiceAccountName = async (req: Request, res: Response) => {
|
||||||
|
const { serviceAccountId } = req.params;
|
||||||
|
const { name } = req.body;
|
||||||
|
|
||||||
|
const serviceAccount = await ServiceAccount.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
_id: new Types.ObjectId(serviceAccountId)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a service account key to service account with id [serviceAccountId]
|
||||||
|
* for workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const addServiceAccountKey = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
workspaceId,
|
||||||
|
encryptedKey,
|
||||||
|
nonce
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
const serviceAccountKey = await new ServiceAccountKey({
|
||||||
|
encryptedKey,
|
||||||
|
nonce,
|
||||||
|
sender: req.user._id,
|
||||||
|
serviceAccount: req.serviceAccount._d,
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
return serviceAccountKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return workspace-level permission for service account with id [serviceAccountId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const getServiceAccountWorkspacePermissions = async (req: Request, res: Response) => {
|
||||||
|
const serviceAccountWorkspacePermissions = await ServiceAccountWorkspacePermission.find({
|
||||||
|
serviceAccount: req.serviceAccount._id
|
||||||
|
}).populate('workspace');
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccountWorkspacePermissions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a workspace permission to service account with id [serviceAccountId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const addServiceAccountWorkspacePermission = async (req: Request, res: Response) => {
|
||||||
|
const { serviceAccountId } = req.params;
|
||||||
|
const {
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
read = false,
|
||||||
|
write = false,
|
||||||
|
encryptedKey,
|
||||||
|
nonce
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
if (!req.membership.workspace.environments.some((e: { name: string; slug: string }) => e.slug === environment)) {
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to validate workspace environment'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingPermission = await ServiceAccountWorkspacePermission.findOne({
|
||||||
|
serviceAccount: new Types.ObjectId(serviceAccountId),
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingPermission) throw BadRequestError({ message: 'Failed to add workspace permission to service account due to already-existing ' });
|
||||||
|
|
||||||
|
const serviceAccountWorkspacePermission = await new ServiceAccountWorkspacePermission({
|
||||||
|
serviceAccount: new Types.ObjectId(serviceAccountId),
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
read,
|
||||||
|
write
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
const existingServiceAccountKey = await ServiceAccountKey.findOne({
|
||||||
|
serviceAccount: new Types.ObjectId(serviceAccountId),
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingServiceAccountKey) {
|
||||||
|
await new ServiceAccountKey({
|
||||||
|
encryptedKey,
|
||||||
|
nonce,
|
||||||
|
sender: req.user._id,
|
||||||
|
serviceAccount: new Types.ObjectId(serviceAccountId),
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
}).save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccountWorkspacePermission
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete workspace permission from service account with id [serviceAccountId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const deleteServiceAccountWorkspacePermission = async (req: Request, res: Response) => {
|
||||||
|
const { serviceAccountWorkspacePermissionId } = req.params;
|
||||||
|
const serviceAccountWorkspacePermission = await ServiceAccountWorkspacePermission.findByIdAndDelete(serviceAccountWorkspacePermissionId);
|
||||||
|
|
||||||
|
if (serviceAccountWorkspacePermission) {
|
||||||
|
const { serviceAccount, workspace } = serviceAccountWorkspacePermission;
|
||||||
|
const count = await ServiceAccountWorkspacePermission.countDocuments({
|
||||||
|
serviceAccount,
|
||||||
|
workspace
|
||||||
|
});
|
||||||
|
|
||||||
|
if (count === 0) {
|
||||||
|
await ServiceAccountKey.findOneAndDelete({
|
||||||
|
serviceAccount,
|
||||||
|
workspace
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccountWorkspacePermission
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete service account with id [serviceAccountId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const deleteServiceAccount = async (req: Request, res: Response) => {
|
||||||
|
const { serviceAccountId } = req.params;
|
||||||
|
|
||||||
|
const serviceAccount = await ServiceAccount.findByIdAndDelete(serviceAccountId);
|
||||||
|
|
||||||
|
if (serviceAccount) {
|
||||||
|
await ServiceAccountKey.deleteMany({
|
||||||
|
serviceAccount: serviceAccount._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await ServiceAccountOrganizationPermission.deleteMany({
|
||||||
|
serviceAccount: new Types.ObjectId(serviceAccountId)
|
||||||
|
});
|
||||||
|
|
||||||
|
await ServiceAccountWorkspacePermission.deleteMany({
|
||||||
|
serviceAccount: new Types.ObjectId(serviceAccountId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return service account keys for service account with id [serviceAccountId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getServiceAccountKeys = async (req: Request, res: Response) => {
|
||||||
|
const workspaceId = req.query.workspaceId as string;
|
||||||
|
|
||||||
|
const serviceAccountKeys = await ServiceAccountKey.find({
|
||||||
|
serviceAccount: req.serviceAccount._id,
|
||||||
|
...(workspaceId ? { workspace: new Types.ObjectId(workspaceId) } : {})
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceAccountKeys
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -3,10 +3,16 @@ import { Request, Response } from 'express';
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
import {
|
import {
|
||||||
|
User,
|
||||||
|
ServiceAccount,
|
||||||
ServiceTokenData
|
ServiceTokenData
|
||||||
} from '../../models';
|
} from '../../models';
|
||||||
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
|
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
|
||||||
import { ABILITY_READ } from '../../variables/organization';
|
import {
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
|
} from '../../variables';
|
||||||
import { getSaltRounds } from '../../config';
|
import { getSaltRounds } from '../../config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,59 +59,60 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const createServiceTokenData = async (req: Request, res: Response) => {
|
export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||||
let serviceToken, serviceTokenData;
|
let serviceTokenData;
|
||||||
|
|
||||||
try {
|
const {
|
||||||
const {
|
name,
|
||||||
name,
|
workspaceId,
|
||||||
workspaceId,
|
environment,
|
||||||
environment,
|
encryptedKey,
|
||||||
encryptedKey,
|
iv,
|
||||||
iv,
|
tag,
|
||||||
tag,
|
expiresIn,
|
||||||
expiresIn,
|
permissions
|
||||||
permissions
|
} = req.body;
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_READ)
|
const secret = crypto.randomBytes(16).toString('hex');
|
||||||
if (!hasAccess) {
|
const secretHash = await bcrypt.hash(secret, getSaltRounds());
|
||||||
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const secret = crypto.randomBytes(16).toString('hex');
|
let expiresAt;
|
||||||
const secretHash = await bcrypt.hash(secret, getSaltRounds());
|
if (!!expiresIn) {
|
||||||
|
expiresAt = new Date()
|
||||||
const expiresAt = new Date();
|
|
||||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||||
|
|
||||||
serviceTokenData = await new ServiceTokenData({
|
|
||||||
name,
|
|
||||||
workspace: workspaceId,
|
|
||||||
environment,
|
|
||||||
user: req.user._id,
|
|
||||||
expiresAt,
|
|
||||||
secretHash,
|
|
||||||
encryptedKey,
|
|
||||||
iv,
|
|
||||||
tag,
|
|
||||||
permissions
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
// return service token data without sensitive data
|
|
||||||
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);
|
|
||||||
|
|
||||||
if (!serviceTokenData) throw new Error('Failed to find service token data');
|
|
||||||
|
|
||||||
serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to create service token data'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let user, serviceAccount;
|
||||||
|
|
||||||
|
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User) {
|
||||||
|
user = req.authData.authPayload._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && req.authData.authPayload instanceof ServiceAccount) {
|
||||||
|
serviceAccount = req.authData.authPayload._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceTokenData = await new ServiceTokenData({
|
||||||
|
name,
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment,
|
||||||
|
user,
|
||||||
|
serviceAccount,
|
||||||
|
lastUsed: new Date(),
|
||||||
|
expiresAt,
|
||||||
|
secretHash,
|
||||||
|
encryptedKey,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
permissions
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
// return service token data without sensitive data
|
||||||
|
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);
|
||||||
|
|
||||||
|
if (!serviceTokenData) throw new Error('Failed to find service token data');
|
||||||
|
|
||||||
|
const serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceToken,
|
serviceToken,
|
||||||
serviceTokenData
|
serviceTokenData
|
||||||
@@ -119,25 +126,11 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
||||||
let serviceTokenData;
|
const { serviceTokenDataId } = req.params;
|
||||||
try {
|
|
||||||
const { serviceTokenDataId } = req.params;
|
|
||||||
|
|
||||||
serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
|
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to delete service token data'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceTokenData
|
serviceTokenData
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function UnauthorizedRequestError(arg0: { message: string; }) {
|
|
||||||
throw new Error('Function not implemented.');
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import { issueAuthTokens } from '../../helpers/auth';
|
import { issueAuthTokens } from '../../helpers/auth';
|
||||||
import { INVITED, ACCEPTED } from '../../variables';
|
import { INVITED, ACCEPTED } from '../../variables';
|
||||||
import request from '../../config/request';
|
import request from '../../config/request';
|
||||||
import { getNodeEnv, getLoopsApiKey } from '../../config';
|
import { getLoopsApiKey, getHttpsEnabled } from '../../config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete setting up user by adding their personal and auth information as part of the
|
* Complete setting up user by adding their personal and auth information as part of the
|
||||||
@@ -24,9 +24,9 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
|||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
protectedKey,
|
protectedKey,
|
||||||
protectedKeyIV,
|
protectedKeyIV,
|
||||||
protectedKeyTag,
|
protectedKeyTag,
|
||||||
publicKey,
|
publicKey,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
encryptedPrivateKeyIV,
|
encryptedPrivateKeyIV,
|
||||||
@@ -38,9 +38,9 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
|||||||
email: string;
|
email: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
protectedKey: string;
|
protectedKey: string;
|
||||||
protectedKeyIV: string;
|
protectedKeyIV: string;
|
||||||
protectedKeyTag: string;
|
protectedKeyTag: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
encryptedPrivateKey: string;
|
encryptedPrivateKey: string;
|
||||||
encryptedPrivateKeyIV: string;
|
encryptedPrivateKeyIV: string;
|
||||||
@@ -48,7 +48,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
|||||||
salt: string;
|
salt: string;
|
||||||
verifier: string;
|
verifier: string;
|
||||||
organizationName: string;
|
organizationName: string;
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
// get user
|
// get user
|
||||||
user = await User.findOne({ email });
|
user = await User.findOne({ email });
|
||||||
@@ -66,10 +66,10 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
|||||||
userId: user._id.toString(),
|
userId: user._id.toString(),
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
encryptionVersion: 2,
|
encryptionVersion: 2,
|
||||||
protectedKey,
|
protectedKey,
|
||||||
protectedKeyIV,
|
protectedKeyIV,
|
||||||
protectedKeyTag,
|
protectedKeyTag,
|
||||||
publicKey,
|
publicKey,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
encryptedPrivateKeyIV,
|
encryptedPrivateKeyIV,
|
||||||
@@ -127,7 +127,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
|||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
secure: getNodeEnv() === 'production' ? true : false
|
secure: getHttpsEnabled()
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
@@ -158,9 +158,9 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
|
|||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
protectedKey,
|
protectedKey,
|
||||||
protectedKeyIV,
|
protectedKeyIV,
|
||||||
protectedKeyTag,
|
protectedKeyTag,
|
||||||
publicKey,
|
publicKey,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
encryptedPrivateKeyIV,
|
encryptedPrivateKeyIV,
|
||||||
@@ -192,10 +192,10 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
|
|||||||
userId: user._id.toString(),
|
userId: user._id.toString(),
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
encryptionVersion: 2,
|
encryptionVersion: 2,
|
||||||
protectedKey,
|
protectedKey,
|
||||||
protectedKeyIV,
|
protectedKeyIV,
|
||||||
protectedKeyTag,
|
protectedKeyTag,
|
||||||
publicKey,
|
publicKey,
|
||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
encryptedPrivateKeyIV,
|
encryptedPrivateKeyIV,
|
||||||
@@ -232,7 +232,7 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
|
|||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
secure: getNodeEnv() === 'production' ? true : false
|
secure: getHttpsEnabled()
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
reformatPullSecrets
|
reformatPullSecrets
|
||||||
} from '../../helpers/secret';
|
} from '../../helpers/secret';
|
||||||
import { pushKeys } from '../../helpers/key';
|
import { pushKeys } from '../../helpers/key';
|
||||||
import { getPostHogClient, EventService } from '../../services';
|
import { TelemetryService, EventService } from '../../services';
|
||||||
import { eventPushSecrets } from '../../events';
|
import { eventPushSecrets } from '../../events';
|
||||||
|
|
||||||
interface V2PushSecret {
|
interface V2PushSecret {
|
||||||
@@ -48,7 +48,7 @@ interface V2PushSecret {
|
|||||||
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||||
// upload (encrypted) secrets to workspace with id [workspaceId]
|
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||||
try {
|
try {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
||||||
const { keys, environment, channel } = req.body;
|
const { keys, environment, channel } = req.body;
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
@@ -122,7 +122,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
|||||||
export const pullSecrets = async (req: Request, res: Response) => {
|
export const pullSecrets = async (req: Request, res: Response) => {
|
||||||
let secrets;
|
let secrets;
|
||||||
try {
|
try {
|
||||||
const postHogClient = getPostHogClient();
|
const postHogClient = TelemetryService.getPostHogClient();
|
||||||
const environment: string = req.query.environment as string;
|
const environment: string = req.query.environment as string;
|
||||||
const channel: string = req.query.channel as string;
|
const channel: string = req.query.channel as string;
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
@@ -507,4 +507,3 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
workspace
|
workspace
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { Request, Response } from "express";
|
|||||||
import { Membership, Workspace } from "../../../models";
|
import { Membership, Workspace } from "../../../models";
|
||||||
import { IMembershipPermission } from "../../../models/membership";
|
import { IMembershipPermission } from "../../../models/membership";
|
||||||
import { BadRequestError, UnauthorizedRequestError } from "../../../utils/errors";
|
import { BadRequestError, UnauthorizedRequestError } from "../../../utils/errors";
|
||||||
import { ABILITY_READ, ABILITY_WRITE, ADMIN, MEMBER } from "../../../variables/organization";
|
import { ADMIN, MEMBER } from "../../../variables/organization";
|
||||||
|
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../../variables';
|
||||||
import { Builder } from "builder-pattern"
|
import { Builder } from "builder-pattern"
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ export const denyMembershipPermissions = async (req: Request, res: Response) =>
|
|||||||
const { membershipId } = req.params;
|
const { membershipId } = req.params;
|
||||||
const { permissions } = req.body;
|
const { permissions } = req.body;
|
||||||
const sanitizedMembershipPermissions: IMembershipPermission[] = permissions.map((permission: IMembershipPermission) => {
|
const sanitizedMembershipPermissions: IMembershipPermission[] = permissions.map((permission: IMembershipPermission) => {
|
||||||
if (!permission.ability || !permission.environmentSlug || ![ABILITY_READ, ABILITY_WRITE].includes(permission.ability)) {
|
if (!permission.ability || !permission.environmentSlug || ![PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS].includes(permission.ability)) {
|
||||||
throw BadRequestError({ message: "One or more required fields are missing from the request or have incorrect type" })
|
throw BadRequestError({ message: "One or more required fields are missing from the request or have incorrect type" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -418,7 +418,7 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
|
|||||||
.skip(offset)
|
.skip(offset)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.populate('actions')
|
.populate('actions')
|
||||||
.populate('user');
|
.populate('user serviceAccount serviceTokenData');
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser({ email: req.user.email });
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
|||||||
@@ -24,11 +24,15 @@ import {
|
|||||||
const createActionUpdateSecret = async ({
|
const createActionUpdateSecret = async ({
|
||||||
name,
|
name,
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds
|
secretIds
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
secretIds: Types.ObjectId[];
|
secretIds: Types.ObjectId[];
|
||||||
}) => {
|
}) => {
|
||||||
@@ -46,6 +50,8 @@ const createActionUpdateSecret = async ({
|
|||||||
action = await new Action({
|
action = await new Action({
|
||||||
name,
|
name,
|
||||||
user: userId,
|
user: userId,
|
||||||
|
serviceAccount: serviceAccountId,
|
||||||
|
serviceTokenData: serviceTokenDataId,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
payload: {
|
payload: {
|
||||||
secretVersions: latestSecretVersions
|
secretVersions: latestSecretVersions
|
||||||
@@ -72,11 +78,15 @@ const createActionUpdateSecret = async ({
|
|||||||
const createActionSecret = async ({
|
const createActionSecret = async ({
|
||||||
name,
|
name,
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds
|
secretIds
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
secretIds: Types.ObjectId[];
|
secretIds: Types.ObjectId[];
|
||||||
}) => {
|
}) => {
|
||||||
@@ -94,6 +104,8 @@ const createActionSecret = async ({
|
|||||||
action = await new Action({
|
action = await new Action({
|
||||||
name,
|
name,
|
||||||
user: userId,
|
user: userId,
|
||||||
|
serviceAccount: serviceAccountId,
|
||||||
|
serviceTokenData: serviceTokenDataId,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
payload: {
|
payload: {
|
||||||
secretVersions: latestSecretVersions
|
secretVersions: latestSecretVersions
|
||||||
@@ -110,29 +122,36 @@ const createActionSecret = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an (audit) action for user with id [userId]
|
* Create an (audit) action for client with id [userId],
|
||||||
|
* [serviceAccountId], or [serviceTokenDataId]
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String} obj.name - name of action
|
* @param {String} obj.name - name of action
|
||||||
* @param {String} obj.userId - id of user associated with action
|
* @param {String} obj.userId - id of user associated with action
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const createActionUser = ({
|
const createActionClient = ({
|
||||||
name,
|
name,
|
||||||
userId
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
}) => {
|
}) => {
|
||||||
let action;
|
let action;
|
||||||
try {
|
try {
|
||||||
action = new Action({
|
action = new Action({
|
||||||
name,
|
name,
|
||||||
user: userId
|
user: userId,
|
||||||
|
serviceAccount: serviceAccountId,
|
||||||
|
serviceTokenData: serviceTokenDataId
|
||||||
}).save();
|
}).save();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to create user action');
|
throw new Error('Failed to create client action');
|
||||||
}
|
}
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
@@ -149,11 +168,15 @@ const createActionUser = ({
|
|||||||
const createActionHelper = async ({
|
const createActionHelper = async ({
|
||||||
name,
|
name,
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds,
|
secretIds,
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
workspaceId?: Types.ObjectId;
|
workspaceId?: Types.ObjectId;
|
||||||
secretIds?: Types.ObjectId[];
|
secretIds?: Types.ObjectId[];
|
||||||
}) => {
|
}) => {
|
||||||
@@ -162,7 +185,7 @@ const createActionHelper = async ({
|
|||||||
switch (name) {
|
switch (name) {
|
||||||
case ACTION_LOGIN:
|
case ACTION_LOGIN:
|
||||||
case ACTION_LOGOUT:
|
case ACTION_LOGOUT:
|
||||||
action = await createActionUser({
|
action = await createActionClient({
|
||||||
name,
|
name,
|
||||||
userId
|
userId
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { Membership } from "../../models";
|
import { Membership } from "../../models";
|
||||||
import { ABILITY_READ, ABILITY_WRITE } from "../../variables/organization";
|
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../variables';
|
||||||
|
|
||||||
export const userHasWorkspaceAccess = async (userId: any, workspaceId: any, environment: any, action: any) => {
|
export const userHasWorkspaceAccess = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string, action: any) => {
|
||||||
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
||||||
if (!membershipForWorkspace) {
|
if (!membershipForWorkspace) {
|
||||||
return false
|
return false
|
||||||
@@ -18,15 +19,15 @@ export const userHasWorkspaceAccess = async (userId: any, workspaceId: any, envi
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export const userHasWriteOnlyAbility = async (userId: any, workspaceId: any, environment: any) => {
|
export const userHasWriteOnlyAbility = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string) => {
|
||||||
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
||||||
if (!membershipForWorkspace) {
|
if (!membershipForWorkspace) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const deniedMembershipPermissions = membershipForWorkspace.deniedPermissions;
|
const deniedMembershipPermissions = membershipForWorkspace.deniedPermissions;
|
||||||
const isWriteDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: ABILITY_WRITE });
|
const isWriteDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
|
||||||
const isReadDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: ABILITY_READ });
|
const isReadDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
|
||||||
|
|
||||||
// case: you have write only if read is blocked and write is not
|
// case: you have write only if read is blocked and write is not
|
||||||
if (isReadDisallowed && !isWriteDisallowed) {
|
if (isReadDisallowed && !isWriteDisallowed) {
|
||||||
@@ -36,15 +37,15 @@ export const userHasWriteOnlyAbility = async (userId: any, workspaceId: any, env
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export const userHasNoAbility = async (userId: any, workspaceId: any, environment: any) => {
|
export const userHasNoAbility = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string) => {
|
||||||
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
|
||||||
if (!membershipForWorkspace) {
|
if (!membershipForWorkspace) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const deniedMembershipPermissions = membershipForWorkspace.deniedPermissions;
|
const deniedMembershipPermissions = membershipForWorkspace.deniedPermissions;
|
||||||
const isWriteDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: ABILITY_WRITE });
|
const isWriteDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
|
||||||
const isReadBlocked = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: ABILITY_READ });
|
const isReadBlocked = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
|
||||||
|
|
||||||
if (isReadBlocked && isWriteDisallowed) {
|
if (isReadBlocked && isWriteDisallowed) {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -16,12 +16,16 @@ import {
|
|||||||
*/
|
*/
|
||||||
const createLogHelper = async ({
|
const createLogHelper = async ({
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
actions,
|
actions,
|
||||||
channel,
|
channel,
|
||||||
ipAddress
|
ipAddress
|
||||||
}: {
|
}: {
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
workspaceId?: Types.ObjectId;
|
workspaceId?: Types.ObjectId;
|
||||||
actions: IAction[];
|
actions: IAction[];
|
||||||
channel: string;
|
channel: string;
|
||||||
@@ -31,6 +35,8 @@ const createLogHelper = async ({
|
|||||||
try {
|
try {
|
||||||
log = await new Log({
|
log = await new Log({
|
||||||
user: userId,
|
user: userId,
|
||||||
|
serviceAccount: serviceAccountId,
|
||||||
|
serviceTokenData: serviceTokenDataId,
|
||||||
workspace: workspaceId ?? undefined,
|
workspace: workspaceId ?? undefined,
|
||||||
actionNames: actions.map((a) => a.name),
|
actionNames: actions.map((a) => a.name),
|
||||||
actions,
|
actions,
|
||||||
|
|||||||
@@ -15,32 +15,28 @@ import {
|
|||||||
const requireSecretSnapshotAuth = ({
|
const requireSecretSnapshotAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
const { secretSnapshotId } = req.params;
|
||||||
const { secretSnapshotId } = req.params;
|
|
||||||
|
|
||||||
const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId);
|
const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId);
|
||||||
|
|
||||||
if (!secretSnapshot) {
|
if (!secretSnapshot) {
|
||||||
return next(SecretSnapshotNotFoundError({
|
return next(SecretSnapshotNotFoundError({
|
||||||
message: 'Failed to find secret snapshot'
|
message: 'Failed to find secret snapshot'
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
await validateMembership({
|
|
||||||
userId: req.user._id.toString(),
|
|
||||||
workspaceId: secretSnapshot.workspace.toString(),
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
|
|
||||||
req.secretSnapshot = secretSnapshot as any;
|
|
||||||
|
|
||||||
next();
|
|
||||||
} catch (err) {
|
|
||||||
return next(UnauthorizedRequestError({ message: 'Unable to authenticate secret snapshot' }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await validateMembership({
|
||||||
|
userId: req.user._id,
|
||||||
|
workspaceId: secretSnapshot.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
req.secretSnapshot = secretSnapshot as any;
|
||||||
|
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
export interface IAction {
|
export interface IAction {
|
||||||
name: string;
|
name: string;
|
||||||
user?: Types.ObjectId,
|
user?: Types.ObjectId,
|
||||||
|
serviceAccount?: Types.ObjectId,
|
||||||
|
serviceTokenData?: Types.ObjectId,
|
||||||
workspace?: Types.ObjectId,
|
workspace?: Types.ObjectId,
|
||||||
payload?: {
|
payload?: {
|
||||||
secretVersions?: Types.ObjectId[]
|
secretVersions?: Types.ObjectId[]
|
||||||
@@ -33,8 +35,15 @@ const actionSchema = new Schema<IAction>(
|
|||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'User',
|
ref: 'User'
|
||||||
required: true
|
},
|
||||||
|
serviceAccount: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceAccount'
|
||||||
|
},
|
||||||
|
serviceTokenData: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceTokenData'
|
||||||
},
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
export interface ILog {
|
export interface ILog {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
user?: Types.ObjectId;
|
user?: Types.ObjectId;
|
||||||
|
serviceAccount?: Types.ObjectId;
|
||||||
|
serviceTokenData?: Types.ObjectId;
|
||||||
workspace?: Types.ObjectId;
|
workspace?: Types.ObjectId;
|
||||||
actionNames: string[];
|
actionNames: string[];
|
||||||
actions: Types.ObjectId[];
|
actions: Types.ObjectId[];
|
||||||
@@ -24,6 +26,14 @@ const logSchema = new Schema<ILog>(
|
|||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'User'
|
ref: 'User'
|
||||||
},
|
},
|
||||||
|
serviceAccount: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceAccount'
|
||||||
|
},
|
||||||
|
serviceTokenData: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceTokenData'
|
||||||
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'Workspace'
|
ref: 'Workspace'
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ import {
|
|||||||
} from '../../../middleware';
|
} from '../../../middleware';
|
||||||
import { query, param, body } from 'express-validator';
|
import { query, param, body } from 'express-validator';
|
||||||
import { secretController } from '../../controllers/v1';
|
import { secretController } from '../../controllers/v1';
|
||||||
import { ADMIN, MEMBER } from '../../../variables';
|
import {
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
PERMISSION_WRITE_SECRETS
|
||||||
|
} from '../../../variables';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:secretId/secret-versions',
|
'/:secretId/secret-versions',
|
||||||
@@ -15,7 +20,8 @@ router.get(
|
|||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireSecretAuth({
|
requireSecretAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
requiredPermissions: [PERMISSION_READ_SECRETS]
|
||||||
}),
|
}),
|
||||||
param('secretId').exists().trim(),
|
param('secretId').exists().trim(),
|
||||||
query('offset').exists().isInt(),
|
query('offset').exists().isInt(),
|
||||||
@@ -30,7 +36,8 @@ router.post(
|
|||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireSecretAuth({
|
requireSecretAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
requiredPermissions: [PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
param('secretId').exists().trim(),
|
param('secretId').exists().trim(),
|
||||||
body('version').exists().isInt(),
|
body('version').exists().isInt(),
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ router.get(
|
|||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
query('offset').exists().isInt(),
|
query('offset').exists().isInt(),
|
||||||
@@ -30,7 +31,8 @@ router.get(
|
|||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: ['jwt']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -43,7 +45,8 @@ router.post(
|
|||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
body('version').exists().isInt(),
|
body('version').exists().isInt(),
|
||||||
@@ -57,7 +60,8 @@ router.get(
|
|||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: ['jwt', 'apiKey']
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
query('offset').exists().isInt(),
|
query('offset').exists().isInt(),
|
||||||
|
|||||||
@@ -26,12 +26,16 @@ class EELogService {
|
|||||||
*/
|
*/
|
||||||
static async createLog({
|
static async createLog({
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
actions,
|
actions,
|
||||||
channel,
|
channel,
|
||||||
ipAddress
|
ipAddress
|
||||||
}: {
|
}: {
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
workspaceId?: Types.ObjectId;
|
workspaceId?: Types.ObjectId;
|
||||||
actions: IAction[];
|
actions: IAction[];
|
||||||
channel: string;
|
channel: string;
|
||||||
@@ -40,6 +44,8 @@ class EELogService {
|
|||||||
if (!EELicenseService.isLicenseValid) return null;
|
if (!EELicenseService.isLicenseValid) return null;
|
||||||
return await createLogHelper({
|
return await createLogHelper({
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
actions,
|
actions,
|
||||||
channel,
|
channel,
|
||||||
@@ -59,17 +65,23 @@ class EELogService {
|
|||||||
static async createAction({
|
static async createAction({
|
||||||
name,
|
name,
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds
|
secretIds
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
userId: Types.ObjectId;
|
userId?: Types.ObjectId;
|
||||||
|
serviceAccountId?: Types.ObjectId;
|
||||||
|
serviceTokenDataId?: Types.ObjectId;
|
||||||
workspaceId?: Types.ObjectId;
|
workspaceId?: Types.ObjectId;
|
||||||
secretIds?: Types.ObjectId[];
|
secretIds?: Types.ObjectId[];
|
||||||
}) {
|
}) {
|
||||||
return await createActionHelper({
|
return await createActionHelper({
|
||||||
name,
|
name,
|
||||||
userId,
|
userId,
|
||||||
|
serviceAccountId,
|
||||||
|
serviceTokenDataId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
secretIds
|
secretIds
|
||||||
});
|
});
|
||||||
|
|||||||
+146
-122
@@ -1,15 +1,18 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
import {
|
import {
|
||||||
IUser,
|
IUser,
|
||||||
User,
|
User,
|
||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
|
ServiceAccount,
|
||||||
APIKeyData
|
APIKeyData
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import {
|
import {
|
||||||
AccountNotFoundError,
|
AccountNotFoundError,
|
||||||
ServiceTokenDataNotFoundError,
|
ServiceTokenDataNotFoundError,
|
||||||
|
ServiceAccountNotFoundError,
|
||||||
APIKeyDataNotFoundError,
|
APIKeyDataNotFoundError,
|
||||||
UnauthorizedRequestError,
|
UnauthorizedRequestError,
|
||||||
BadRequestError
|
BadRequestError
|
||||||
@@ -20,6 +23,12 @@ import {
|
|||||||
getJwtRefreshLifetime,
|
getJwtRefreshLifetime,
|
||||||
getJwtRefreshSecret
|
getJwtRefreshSecret
|
||||||
} from '../config';
|
} from '../config';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -37,7 +46,7 @@ const validateAuthMode = ({
|
|||||||
const apiKey = headers['x-api-key'];
|
const apiKey = headers['x-api-key'];
|
||||||
const authHeader = headers['authorization'];
|
const authHeader = headers['authorization'];
|
||||||
|
|
||||||
let authTokenType, authTokenValue;
|
let authMode, authTokenValue;
|
||||||
if (apiKey === undefined && authHeader === undefined) {
|
if (apiKey === undefined && authHeader === undefined) {
|
||||||
// case: no auth or X-API-KEY header present
|
// case: no auth or X-API-KEY header present
|
||||||
throw BadRequestError({ message: 'Missing Authorization or X-API-KEY in request header.' });
|
throw BadRequestError({ message: 'Missing Authorization or X-API-KEY in request header.' });
|
||||||
@@ -45,7 +54,7 @@ const validateAuthMode = ({
|
|||||||
|
|
||||||
if (typeof apiKey === 'string') {
|
if (typeof apiKey === 'string') {
|
||||||
// case: treat request authentication type as via X-API-KEY (i.e. API Key)
|
// case: treat request authentication type as via X-API-KEY (i.e. API Key)
|
||||||
authTokenType = 'apiKey';
|
authMode = AUTH_MODE_API_KEY;
|
||||||
authTokenValue = apiKey;
|
authTokenValue = apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,20 +70,24 @@ const validateAuthMode = ({
|
|||||||
|
|
||||||
switch (tokenValue.split('.', 1)[0]) {
|
switch (tokenValue.split('.', 1)[0]) {
|
||||||
case 'st':
|
case 'st':
|
||||||
authTokenType = 'serviceToken';
|
authMode = AUTH_MODE_SERVICE_TOKEN;
|
||||||
|
break;
|
||||||
|
case 'sa':
|
||||||
|
authMode = AUTH_MODE_SERVICE_ACCOUNT;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
authTokenType = 'jwt';
|
authMode = AUTH_MODE_JWT;
|
||||||
}
|
}
|
||||||
|
|
||||||
authTokenValue = tokenValue;
|
authTokenValue = tokenValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authTokenType || !authTokenValue) throw BadRequestError({ message: 'Missing valid Authorization or X-API-KEY in request header.' });
|
if (!authMode || !authTokenValue) throw BadRequestError({ message: 'Missing valid Authorization or X-API-KEY in request header.' });
|
||||||
|
|
||||||
if (!acceptedAuthModes.includes(authTokenType)) throw BadRequestError({ message: 'The provided authentication type is not supported.' });
|
if (!acceptedAuthModes.includes(authMode)) throw BadRequestError({ message: 'The provided authentication type is not supported.' });
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
authTokenType,
|
authMode,
|
||||||
authTokenValue
|
authTokenValue
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -90,25 +103,17 @@ const getAuthUserPayload = async ({
|
|||||||
}: {
|
}: {
|
||||||
authTokenValue: string;
|
authTokenValue: string;
|
||||||
}) => {
|
}) => {
|
||||||
let user;
|
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||||
try {
|
jwt.verify(authTokenValue, getJwtAuthSecret())
|
||||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
);
|
||||||
jwt.verify(authTokenValue, getJwtAuthSecret())
|
|
||||||
);
|
|
||||||
|
|
||||||
user = await User.findOne({
|
const user = await User.findOne({
|
||||||
_id: decodedToken.userId
|
_id: decodedToken.userId
|
||||||
}).select('+publicKey');
|
}).select('+publicKey');
|
||||||
|
|
||||||
if (!user) throw AccountNotFoundError({ message: 'Failed to find User' });
|
if (!user) throw AccountNotFoundError({ message: 'Failed to find User' });
|
||||||
|
|
||||||
if (!user?.publicKey) throw UnauthorizedRequestError({ message: 'Failed to authenticate User with partially set up account' });
|
if (!user?.publicKey) throw UnauthorizedRequestError({ message: 'Failed to authenticate User with partially set up account' });
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed to authenticate JWT token'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@@ -124,45 +129,70 @@ const getAuthSTDPayload = async ({
|
|||||||
}: {
|
}: {
|
||||||
authTokenValue: string;
|
authTokenValue: string;
|
||||||
}) => {
|
}) => {
|
||||||
let serviceTokenData;
|
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split('.', 3);
|
||||||
try {
|
|
||||||
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split('.', 3);
|
|
||||||
|
|
||||||
// TODO: optimize double query
|
let serviceTokenData = await ServiceTokenData
|
||||||
serviceTokenData = await ServiceTokenData
|
.findById(TOKEN_IDENTIFIER, '+secretHash +expiresAt');
|
||||||
.findById(TOKEN_IDENTIFIER, '+secretHash +expiresAt');
|
|
||||||
|
|
||||||
if (!serviceTokenData) {
|
if (!serviceTokenData) {
|
||||||
throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
|
throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
|
||||||
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
|
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
|
||||||
// case: service token expired
|
// case: service token expired
|
||||||
await ServiceTokenData.findByIdAndDelete(serviceTokenData._id);
|
await ServiceTokenData.findByIdAndDelete(serviceTokenData._id);
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed to authenticate expired service token'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMatch = await bcrypt.compare(TOKEN_SECRET, serviceTokenData.secretHash);
|
|
||||||
if (!isMatch) throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed to authenticate service token'
|
|
||||||
});
|
|
||||||
|
|
||||||
serviceTokenData = await ServiceTokenData
|
|
||||||
.findById(TOKEN_IDENTIFIER)
|
|
||||||
.select('+encryptedKey +iv +tag')
|
|
||||||
.populate<{user: IUser}>('user');
|
|
||||||
|
|
||||||
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: 'Failed to authenticate service token'
|
message: 'Failed to authenticate expired service token'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isMatch = await bcrypt.compare(TOKEN_SECRET, serviceTokenData.secretHash);
|
||||||
|
if (!isMatch) throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed to authenticate service token'
|
||||||
|
});
|
||||||
|
|
||||||
|
serviceTokenData = await ServiceTokenData
|
||||||
|
.findOneAndUpdate({
|
||||||
|
_id: new Types.ObjectId(TOKEN_IDENTIFIER)
|
||||||
|
}, {
|
||||||
|
lastUsed: new Date()
|
||||||
|
}, {
|
||||||
|
new: true
|
||||||
|
})
|
||||||
|
.select('+encryptedKey +iv +tag').populate('user serviceAccount');
|
||||||
|
|
||||||
|
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
|
||||||
|
|
||||||
return serviceTokenData;
|
return serviceTokenData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return service account access key payload
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.authTokenValue - service account access token value
|
||||||
|
* @returns {ServiceAccount} serviceAccount
|
||||||
|
*/
|
||||||
|
const getAuthSAAKPayload = async ({
|
||||||
|
authTokenValue
|
||||||
|
}: {
|
||||||
|
authTokenValue: string;
|
||||||
|
}) => {
|
||||||
|
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split('.', 3);
|
||||||
|
|
||||||
|
const serviceAccount = await ServiceAccount.findById(
|
||||||
|
Buffer.from(TOKEN_IDENTIFIER, 'base64').toString('hex')
|
||||||
|
).select('+secretHash');
|
||||||
|
|
||||||
|
if (!serviceAccount) {
|
||||||
|
throw ServiceAccountNotFoundError({ message: 'Failed to find service account' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await bcrypt.compare(TOKEN_SECRET, serviceAccount.secretHash);
|
||||||
|
if (!result) throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed to authenticate service account access key'
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceAccount;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return API key data payload corresponding to API key [authTokenValue]
|
* Return API key data payload corresponding to API key [authTokenValue]
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
@@ -174,33 +204,44 @@ const getAuthAPIKeyPayload = async ({
|
|||||||
}: {
|
}: {
|
||||||
authTokenValue: string;
|
authTokenValue: string;
|
||||||
}) => {
|
}) => {
|
||||||
let user;
|
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split('.', 3);
|
||||||
try {
|
|
||||||
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split('.', 3);
|
|
||||||
|
|
||||||
const apiKeyData = await APIKeyData
|
let apiKeyData = await APIKeyData
|
||||||
.findById(TOKEN_IDENTIFIER, '+secretHash +expiresAt')
|
.findById(TOKEN_IDENTIFIER, '+secretHash +expiresAt')
|
||||||
.populate('user', '+publicKey');
|
.populate<{ user: IUser }>('user', '+publicKey');
|
||||||
|
|
||||||
if (!apiKeyData) {
|
if (!apiKeyData) {
|
||||||
throw APIKeyDataNotFoundError({ message: 'Failed to find API key data' });
|
throw APIKeyDataNotFoundError({ message: 'Failed to find API key data' });
|
||||||
} else if (apiKeyData?.expiresAt && new Date(apiKeyData.expiresAt) < new Date()) {
|
} else if (apiKeyData?.expiresAt && new Date(apiKeyData.expiresAt) < new Date()) {
|
||||||
// case: API key expired
|
// case: API key expired
|
||||||
await APIKeyData.findByIdAndDelete(apiKeyData._id);
|
await APIKeyData.findByIdAndDelete(apiKeyData._id);
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed to authenticate expired API key'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMatch = await bcrypt.compare(TOKEN_SECRET, apiKeyData.secretHash);
|
|
||||||
if (!isMatch) throw UnauthorizedRequestError({
|
|
||||||
message: 'Failed to authenticate API key'
|
|
||||||
});
|
|
||||||
|
|
||||||
user = apiKeyData.user;
|
|
||||||
} catch (err) {
|
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: 'Failed to authenticate API key'
|
message: 'Failed to authenticate expired API key'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMatch = await bcrypt.compare(TOKEN_SECRET, apiKeyData.secretHash);
|
||||||
|
if (!isMatch) throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed to authenticate API key'
|
||||||
|
});
|
||||||
|
|
||||||
|
apiKeyData = await APIKeyData.findOneAndUpdate({
|
||||||
|
_id: new Types.ObjectId(TOKEN_IDENTIFIER)
|
||||||
|
}, {
|
||||||
|
lastUsed: new Date()
|
||||||
|
}, {
|
||||||
|
new: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!apiKeyData) {
|
||||||
|
throw APIKeyDataNotFoundError({ message: 'Failed to find API key data' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.findById(apiKeyData.user).select('+publicKey');
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw AccountNotFoundError({
|
||||||
|
message: 'Failed to find user'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,30 +257,23 @@ const getAuthAPIKeyPayload = async ({
|
|||||||
* @return {String} obj.refreshToken - issued refresh token
|
* @return {String} obj.refreshToken - issued refresh token
|
||||||
*/
|
*/
|
||||||
const issueAuthTokens = async ({ userId }: { userId: string }) => {
|
const issueAuthTokens = async ({ userId }: { userId: string }) => {
|
||||||
let token: string;
|
|
||||||
let refreshToken: string;
|
|
||||||
try {
|
|
||||||
// issue tokens
|
|
||||||
token = createToken({
|
|
||||||
payload: {
|
|
||||||
userId
|
|
||||||
},
|
|
||||||
expiresIn: getJwtAuthLifetime(),
|
|
||||||
secret: getJwtAuthSecret()
|
|
||||||
});
|
|
||||||
|
|
||||||
refreshToken = createToken({
|
// issue tokens
|
||||||
payload: {
|
const token = createToken({
|
||||||
userId
|
payload: {
|
||||||
},
|
userId
|
||||||
expiresIn: getJwtRefreshLifetime(),
|
},
|
||||||
secret: getJwtRefreshSecret()
|
expiresIn: getJwtAuthLifetime(),
|
||||||
});
|
secret: getJwtAuthSecret()
|
||||||
} catch (err) {
|
});
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
const refreshToken = createToken({
|
||||||
throw new Error('Failed to issue tokens');
|
payload: {
|
||||||
}
|
userId
|
||||||
|
},
|
||||||
|
expiresIn: getJwtRefreshLifetime(),
|
||||||
|
secret: getJwtRefreshSecret()
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token,
|
token,
|
||||||
@@ -253,19 +287,14 @@ const issueAuthTokens = async ({ userId }: { userId: string }) => {
|
|||||||
* @param {String} obj.userId - id of user whose tokens are cleared.
|
* @param {String} obj.userId - id of user whose tokens are cleared.
|
||||||
*/
|
*/
|
||||||
const clearTokens = async ({ userId }: { userId: string }): Promise<void> => {
|
const clearTokens = async ({ userId }: { userId: string }): Promise<void> => {
|
||||||
try {
|
// increment refreshVersion on user by 1
|
||||||
// increment refreshVersion on user by 1
|
User.findOneAndUpdate({
|
||||||
User.findOneAndUpdate({
|
_id: userId
|
||||||
_id: userId
|
}, {
|
||||||
}, {
|
$inc: {
|
||||||
$inc: {
|
refreshVersion: 1
|
||||||
refreshVersion: 1
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -285,21 +314,16 @@ const createToken = ({
|
|||||||
expiresIn: string | number;
|
expiresIn: string | number;
|
||||||
secret: string;
|
secret: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
return jwt.sign(payload, secret, {
|
||||||
return jwt.sign(payload, secret, {
|
expiresIn
|
||||||
expiresIn
|
});
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to create a token');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
validateAuthMode,
|
validateAuthMode,
|
||||||
getAuthUserPayload,
|
getAuthUserPayload,
|
||||||
getAuthSTDPayload,
|
getAuthSTDPayload,
|
||||||
|
getAuthSAAKPayload,
|
||||||
getAuthAPIKeyPayload,
|
getAuthAPIKeyPayload,
|
||||||
createToken,
|
createToken,
|
||||||
issueAuthTokens,
|
issueAuthTokens,
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
Bot,
|
Bot,
|
||||||
BotKey,
|
BotKey,
|
||||||
Secret,
|
Secret,
|
||||||
ISecret,
|
ISecret,
|
||||||
IUser
|
IUser,
|
||||||
|
User,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
ServiceTokenData
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import {
|
import {
|
||||||
generateKeyPair,
|
generateKeyPair,
|
||||||
@@ -12,8 +18,88 @@ import {
|
|||||||
decryptSymmetric,
|
decryptSymmetric,
|
||||||
decryptAsymmetric
|
decryptAsymmetric
|
||||||
} from '../utils/crypto';
|
} from '../utils/crypto';
|
||||||
import { SECRET_SHARED } from '../variables';
|
import {
|
||||||
|
SECRET_SHARED,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
import { getEncryptionKey } from '../config';
|
import { getEncryptionKey } from '../config';
|
||||||
|
import { BotNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
import {
|
||||||
|
validateMembership
|
||||||
|
} from '../helpers/membership';
|
||||||
|
import {
|
||||||
|
validateUserClientForWorkspace
|
||||||
|
} from '../helpers/user';
|
||||||
|
import {
|
||||||
|
validateServiceAccountClientForWorkspace
|
||||||
|
} from '../helpers/serviceAccount';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for bot with id [botId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.botId - id of bot to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
*/
|
||||||
|
const validateClientForBot = async ({
|
||||||
|
authData,
|
||||||
|
botId,
|
||||||
|
acceptedRoles
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
botId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
}) => {
|
||||||
|
const bot = await Bot.findById(botId);
|
||||||
|
|
||||||
|
if (!bot) throw BotNotFoundError();
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: bot.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: bot.workspace
|
||||||
|
});
|
||||||
|
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for bot'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: bot.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw BotNotFoundError({
|
||||||
|
message: 'Failed client authorization for bot'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an inactive bot with name [name] for workspace with id [workspaceId]
|
* Create an inactive bot with name [name] for workspace with id [workspaceId]
|
||||||
@@ -222,6 +308,7 @@ const decryptSymmetricHelper = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
validateClientForBot,
|
||||||
createBot,
|
createBot,
|
||||||
getSecretsHelper,
|
getSecretsHelper,
|
||||||
encryptSymmetricHelper,
|
encryptSymmetricHelper,
|
||||||
|
|||||||
@@ -1,17 +1,42 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
Bot,
|
Bot,
|
||||||
Integration,
|
Integration,
|
||||||
IntegrationAuth,
|
IntegrationAuth,
|
||||||
|
IUser,
|
||||||
|
User,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
ServiceTokenData
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
|
import { exchangeCode, exchangeRefresh, syncSecrets } from '../integrations';
|
||||||
import { BotService } from '../services';
|
import { BotService } from '../services';
|
||||||
import {
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY,
|
||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY
|
INTEGRATION_NETLIFY
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import {
|
||||||
|
UnauthorizedRequestError,
|
||||||
|
IntegrationAuthNotFoundError,
|
||||||
|
IntegrationNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
import RequestError from '../utils/requestError';
|
import RequestError from '../utils/requestError';
|
||||||
|
import {
|
||||||
|
validateClientForIntegrationAuth
|
||||||
|
} from '../helpers/integrationAuth';
|
||||||
|
import {
|
||||||
|
validateUserClientForWorkspace
|
||||||
|
} from '../helpers/user';
|
||||||
|
import {
|
||||||
|
validateServiceAccountClientForWorkspace
|
||||||
|
} from '../helpers/serviceAccount';
|
||||||
|
import { IntegrationService } from '../services';
|
||||||
|
|
||||||
interface Update {
|
interface Update {
|
||||||
workspace: string;
|
workspace: string;
|
||||||
@@ -20,6 +45,84 @@ interface Update {
|
|||||||
accountId?: string;
|
accountId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for integration with id [integrationId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.integrationId - id of integration to validate against
|
||||||
|
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateClientForIntegration = async ({
|
||||||
|
authData,
|
||||||
|
integrationId,
|
||||||
|
acceptedRoles
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
integrationId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const integration = await Integration.findById(integrationId);
|
||||||
|
if (!integration) throw IntegrationNotFoundError();
|
||||||
|
|
||||||
|
const integrationAuth = await IntegrationAuth
|
||||||
|
.findById(integration.integrationAuth)
|
||||||
|
.select(
|
||||||
|
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!integrationAuth) throw IntegrationAuthNotFoundError();
|
||||||
|
|
||||||
|
const accessToken = (await IntegrationService.getIntegrationAuthAccess({
|
||||||
|
integrationAuthId: integrationAuth._id
|
||||||
|
})).accessToken;
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: integration.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ integration, accessToken });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: integration.workspace
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ integration, accessToken });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for integration'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: integration.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ integration, accessToken });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for integration'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform OAuth2 code-token exchange for workspace with id [workspaceId] and integration
|
* Perform OAuth2 code-token exchange for workspace with id [workspaceId] and integration
|
||||||
* named [integration]
|
* named [integration]
|
||||||
@@ -140,7 +243,7 @@ const syncIntegrationsHelper = async ({
|
|||||||
|
|
||||||
// get integration auth access token
|
// get integration auth access token
|
||||||
const access = await getIntegrationAuthAccessHelper({
|
const access = await getIntegrationAuthAccessHelper({
|
||||||
integrationAuthId: integration.integrationAuth.toString()
|
integrationAuthId: integration.integrationAuth
|
||||||
});
|
});
|
||||||
|
|
||||||
// sync secrets to integration
|
// sync secrets to integration
|
||||||
@@ -167,7 +270,7 @@ const syncIntegrationsHelper = async ({
|
|||||||
* @param {String} obj.integrationAuthId - id of integration auth
|
* @param {String} obj.integrationAuthId - id of integration auth
|
||||||
* @param {String} refreshToken - decrypted refresh token
|
* @param {String} refreshToken - decrypted refresh token
|
||||||
*/
|
*/
|
||||||
const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
|
const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||||
let refreshToken;
|
let refreshToken;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -204,7 +307,7 @@ const syncIntegrationsHelper = async ({
|
|||||||
* @param {String} obj.integrationAuthId - id of integration auth
|
* @param {String} obj.integrationAuthId - id of integration auth
|
||||||
* @returns {String} accessToken - decrypted access token
|
* @returns {String} accessToken - decrypted access token
|
||||||
*/
|
*/
|
||||||
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
|
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
|
||||||
let accessId;
|
let accessId;
|
||||||
let accessToken;
|
let accessToken;
|
||||||
try {
|
try {
|
||||||
@@ -367,6 +470,7 @@ const setIntegrationAuthAccessHelper = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
validateClientForIntegration,
|
||||||
handleOAuthExchangeHelper,
|
handleOAuthExchangeHelper,
|
||||||
syncIntegrationsHelper,
|
syncIntegrationsHelper,
|
||||||
getIntegrationAuthRefreshHelper,
|
getIntegrationAuthRefreshHelper,
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
IntegrationAuth,
|
||||||
|
IUser,
|
||||||
|
User,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
ServiceTokenData,
|
||||||
|
IWorkspace
|
||||||
|
} from '../models';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
import {
|
||||||
|
IntegrationAuthNotFoundError,
|
||||||
|
UnauthorizedRequestError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import { IntegrationService } from '../services';
|
||||||
|
import { validateUserClientForWorkspace } from '../helpers/user';
|
||||||
|
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for integration authorization with id [integrationAuthId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.integrationAuthId - id of integration authorization to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateClientForIntegrationAuth = async ({
|
||||||
|
authData,
|
||||||
|
integrationAuthId,
|
||||||
|
acceptedRoles,
|
||||||
|
attachAccessToken
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
integrationAuthId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
attachAccessToken?: boolean;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const integrationAuth = await IntegrationAuth
|
||||||
|
.findById(integrationAuthId)
|
||||||
|
.populate<{ workspace: IWorkspace }>('workspace')
|
||||||
|
.select(
|
||||||
|
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!integrationAuth) throw IntegrationAuthNotFoundError();
|
||||||
|
|
||||||
|
let accessToken;
|
||||||
|
if (attachAccessToken) {
|
||||||
|
accessToken = (await IntegrationService.getIntegrationAuthAccess({
|
||||||
|
integrationAuthId: integrationAuth._id
|
||||||
|
})).accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: integrationAuth.workspace._id,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ integrationAuth, accessToken });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: integrationAuth.workspace._id
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ integrationAuth, accessToken });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for integration authorization'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: integrationAuth.workspace._id,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ integrationAuth, accessToken });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for integration authorization'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
validateClientForIntegrationAuth
|
||||||
|
};
|
||||||
@@ -1,5 +1,106 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Membership, Key } from '../models';
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
Membership,
|
||||||
|
Key,
|
||||||
|
IUser,
|
||||||
|
User,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
ServiceTokenData
|
||||||
|
} from '../models';
|
||||||
|
import {
|
||||||
|
MembershipNotFoundError,
|
||||||
|
BadRequestError,
|
||||||
|
UnauthorizedRequestError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
import {
|
||||||
|
validateUserClientForWorkspace
|
||||||
|
} from '../helpers/user';
|
||||||
|
import {
|
||||||
|
validateServiceAccountClientForWorkspace
|
||||||
|
} from '../helpers/serviceAccount';
|
||||||
|
import {
|
||||||
|
validateServiceTokenDataClientForWorkspace
|
||||||
|
} from '../helpers/serviceTokenData';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for membership with id [membershipId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.membershipId - id of membership to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspaceRoles
|
||||||
|
* @returns {Membership} - validated membership
|
||||||
|
*/
|
||||||
|
const validateClientForMembership = async ({
|
||||||
|
authData,
|
||||||
|
membershipId,
|
||||||
|
acceptedRoles
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
membershipId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const membership = await Membership.findById(membershipId);
|
||||||
|
|
||||||
|
if (!membership) throw MembershipNotFoundError({
|
||||||
|
message: 'Failed to find membership'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: membership.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: membership.workspace
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: authData.authPayload,
|
||||||
|
workspaceId: new Types.ObjectId(membership.workspace)
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode == AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: membership.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for membership'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that user with id [userId] is a member of workspace with id [workspaceId]
|
* Validate that user with id [userId] is a member of workspace with id [workspaceId]
|
||||||
@@ -14,28 +115,24 @@ const validateMembership = async ({
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
}: {
|
}: {
|
||||||
userId: string;
|
userId: Types.ObjectId;
|
||||||
workspaceId: string;
|
workspaceId: Types.ObjectId;
|
||||||
acceptedRoles: string[];
|
acceptedRoles?: Array<'admin' | 'member'>;
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
let membership;
|
const membership = await Membership.findOne({
|
||||||
//TODO: Refactor code to take advantage of using RequestError. It's possible to create new types of errors for more detailed errors
|
user: userId,
|
||||||
try {
|
workspace: workspaceId
|
||||||
membership = await Membership.findOne({
|
}).populate("workspace");
|
||||||
user: userId,
|
|
||||||
workspace: workspaceId
|
|
||||||
}).populate("workspace");
|
|
||||||
|
|
||||||
if (!membership) throw new Error('Failed to find membership');
|
if (!membership) {
|
||||||
|
throw MembershipNotFoundError({ message: 'Failed to find workspace membership' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (acceptedRoles) {
|
||||||
if (!acceptedRoles.includes(membership.role)) {
|
if (!acceptedRoles.includes(membership.role)) {
|
||||||
throw new Error('Failed to validate membership role');
|
throw BadRequestError({ message: 'Failed authorization for membership role' });
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser(null);
|
|
||||||
Sentry.captureException(err);
|
|
||||||
throw new Error('Failed to validate membership');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return membership;
|
return membership;
|
||||||
@@ -133,6 +230,7 @@ const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
validateClientForMembership,
|
||||||
validateMembership,
|
validateMembership,
|
||||||
addMemberships,
|
addMemberships,
|
||||||
findMembership,
|
findMembership,
|
||||||
|
|||||||
@@ -1,40 +1,140 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { MembershipOrg, Workspace, Membership, Key } from '../models';
|
import {
|
||||||
|
MembershipOrg,
|
||||||
|
Workspace,
|
||||||
|
Membership,
|
||||||
|
Key,
|
||||||
|
IUser,
|
||||||
|
User,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
ServiceTokenData
|
||||||
|
} from '../models';
|
||||||
|
import {
|
||||||
|
MembershipOrgNotFoundError,
|
||||||
|
BadRequestError,
|
||||||
|
UnauthorizedRequestError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for organization membership with id [membershipOrgId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.membershipOrgId - id of organization membership to validate against
|
||||||
|
* @param {Array<'owner' | 'admin' | 'member'>} obj.acceptedRoles - accepted organization roles
|
||||||
|
* @param {MembershipOrg} - validated organization membership
|
||||||
|
*/
|
||||||
|
const validateClientForMembershipOrg = async ({
|
||||||
|
authData,
|
||||||
|
membershipOrgId,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
membershipOrgId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
|
}) => {
|
||||||
|
const membershipOrg = await MembershipOrg.findById(membershipOrgId);
|
||||||
|
|
||||||
|
if (!membershipOrg) throw MembershipOrgNotFoundError({
|
||||||
|
message: 'Failed to find organization membership '
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateMembershipOrg({
|
||||||
|
userId: authData.authPayload._id,
|
||||||
|
organizationId: membershipOrg.organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
if (!authData.authPayload.organization.equals(membershipOrg.organization)) throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account client authorization for organization membership'
|
||||||
|
});
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account client authorization for organization membership'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateMembershipOrg({
|
||||||
|
userId: authData.authPayload._id,
|
||||||
|
organizationId: membershipOrg.organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for organization membership'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that user with id [userId] is a member of organization with id [organizationId]
|
* Validate that user with id [userId] is a member of organization with id [organizationId]
|
||||||
* and has at least one of the roles in [acceptedRoles]
|
* and has at least one of the roles in [acceptedRoles]
|
||||||
*
|
* @param {Object} obj
|
||||||
|
* @param {Types.ObjectId} obj.userId
|
||||||
|
* @param {Types.ObjectId} obj.organizationId
|
||||||
|
* @param {String[]} obj.acceptedRoles
|
||||||
*/
|
*/
|
||||||
const validateMembership = async ({
|
const validateMembershipOrg = async ({
|
||||||
userId,
|
userId,
|
||||||
organizationId,
|
organizationId,
|
||||||
acceptedRoles
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
}: {
|
}: {
|
||||||
userId: string;
|
userId: Types.ObjectId;
|
||||||
organizationId: string;
|
organizationId: Types.ObjectId;
|
||||||
acceptedRoles: string[];
|
acceptedRoles?: Array<'owner' | 'admin' | 'member'>;
|
||||||
|
acceptedStatuses?: Array<'invited' | 'accepted'>;
|
||||||
}) => {
|
}) => {
|
||||||
let membership;
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
try {
|
user: userId,
|
||||||
membership = await MembershipOrg.findOne({
|
organization: organizationId
|
||||||
user: new Types.ObjectId(userId),
|
});
|
||||||
organization: new Types.ObjectId(organizationId)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!membership) throw new Error('Failed to find organization membership');
|
if (!membershipOrg) {
|
||||||
|
throw MembershipOrgNotFoundError({ message: '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;
|
if (acceptedRoles) {
|
||||||
|
if (!acceptedRoles.includes(membershipOrg.role)) {
|
||||||
|
throw UnauthorizedRequestError({ message: 'Failed to validate organization membership role' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (acceptedStatuses) {
|
||||||
|
if (!acceptedStatuses.includes(membershipOrg.status)) {
|
||||||
|
throw UnauthorizedRequestError({ message: 'Failed to validate organization membership status' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,7 +256,8 @@ const deleteMembershipOrg = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
validateMembership,
|
validateClientForMembershipOrg,
|
||||||
|
validateMembershipOrg,
|
||||||
findMembershipOrg,
|
findMembershipOrg,
|
||||||
addMembershipsOrg,
|
addMembershipsOrg,
|
||||||
deleteMembershipOrg
|
deleteMembershipOrg
|
||||||
|
|||||||
@@ -1,14 +1,110 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { ACCEPTED } from '../variables';
|
import {
|
||||||
|
IUser,
|
||||||
|
User,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceTokenData,
|
||||||
|
ServiceTokenData
|
||||||
|
} from '../models';
|
||||||
import { Organization, MembershipOrg } from '../models';
|
import { Organization, MembershipOrg } from '../models';
|
||||||
|
import {
|
||||||
|
ACCEPTED,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY,
|
||||||
|
OWNER
|
||||||
|
} from '../variables';
|
||||||
import {
|
import {
|
||||||
getStripeSecretKey,
|
getStripeSecretKey,
|
||||||
getStripeProductPro,
|
getStripeProductPro,
|
||||||
getStripeProductTeam,
|
getStripeProductTeam,
|
||||||
getStripeProductStarter
|
getStripeProductStarter
|
||||||
} from '../config';
|
} from '../config';
|
||||||
|
import {
|
||||||
|
UnauthorizedRequestError,
|
||||||
|
OrganizationNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
validateUserClientForOrganization
|
||||||
|
} from '../helpers/user';
|
||||||
|
import {
|
||||||
|
validateServiceAccountClientForOrganization
|
||||||
|
} from '../helpers/serviceAccount';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate accepted clients for organization with id [organizationId]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.organizationId - id of organization to validate against
|
||||||
|
*/
|
||||||
|
const validateClientForOrganization = async ({
|
||||||
|
authData,
|
||||||
|
organizationId,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
},
|
||||||
|
organizationId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const organization = await Organization.findById(organizationId);
|
||||||
|
|
||||||
|
if (!organization) {
|
||||||
|
throw OrganizationNotFoundError({
|
||||||
|
message: 'Failed to find organization'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
const membershipOrg = await validateUserClientForOrganization({
|
||||||
|
user: authData.authPayload,
|
||||||
|
organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ organization, membershipOrg });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForOrganization({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
organization
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ organization });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for organization'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
const membershipOrg = await validateUserClientForOrganization({
|
||||||
|
user: authData.authPayload,
|
||||||
|
organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ organization, membershipOrg });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for organization'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an organization with name [name]
|
* Create an organization with name [name]
|
||||||
@@ -172,6 +268,7 @@ const updateSubscriptionOrgQuantity = async ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
validateClientForOrganization,
|
||||||
createOrganization,
|
createOrganization,
|
||||||
initSubscriptionOrg,
|
initSubscriptionOrg,
|
||||||
updateSubscriptionOrgQuantity
|
updateSubscriptionOrgQuantity
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const apiLimiter = rateLimit({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 10 requests per minute
|
// 10 requests per minute
|
||||||
const authLimiter = rateLimit({
|
const authLimit = rateLimit({
|
||||||
windowMs: 60 * 1000,
|
windowMs: 60 * 1000,
|
||||||
max: 10,
|
max: 10,
|
||||||
standardHeaders: true,
|
standardHeaders: true,
|
||||||
@@ -36,6 +36,14 @@ const passwordLimiter = rateLimit({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const authLimiter = (req: any, res: any, next: any) => {
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
authLimit(req, res, next);
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
apiLimiter,
|
apiLimiter,
|
||||||
authLimiter,
|
authLimiter,
|
||||||
|
|||||||
@@ -21,60 +21,8 @@ import {
|
|||||||
ACTION_READ_SECRETS
|
ACTION_READ_SECRETS
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { ABILITY_WRITE } from '../variables/organization';
|
|
||||||
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate that user with id [userId] can modify secrets with ids [secretIds]
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object} obj.userId - id of user to validate
|
|
||||||
* @param {Object} obj.secretIds - secret ids
|
|
||||||
* @returns {Secret[]} secrets
|
|
||||||
*/
|
|
||||||
const validateSecrets = async ({
|
|
||||||
userId,
|
|
||||||
secretIds
|
|
||||||
}: {
|
|
||||||
userId: string;
|
|
||||||
secretIds: string[];
|
|
||||||
}) => {
|
|
||||||
let secrets;
|
|
||||||
try {
|
|
||||||
secrets = await Secret.find({
|
|
||||||
_id: {
|
|
||||||
$in: secretIds.map((secretId: string) => new Types.ObjectId(secretId))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (secrets.length != secretIds.length) {
|
|
||||||
throw BadRequestError({ message: 'Unable to validate some secrets' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const userMemberships = await Membership.find({ user: userId })
|
|
||||||
const userMembershipById = _.keyBy(userMemberships, 'workspace');
|
|
||||||
const workspaceIdsSet = new Set(userMemberships.map((m) => m.workspace.toString()));
|
|
||||||
|
|
||||||
// for each secret check if the secret belongs to a workspace the user is a member of
|
|
||||||
secrets.forEach((secret: ISecret) => {
|
|
||||||
if (workspaceIdsSet.has(secret.workspace.toString())) {
|
|
||||||
const deniedMembershipPermissions = userMembershipById[secret.workspace.toString()].deniedPermissions;
|
|
||||||
const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: secret.environment, ability: ABILITY_WRITE });
|
|
||||||
|
|
||||||
if (isDisallowed) {
|
|
||||||
throw UnauthorizedRequestError({ message: 'You do not have the required permissions to perform this action' });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw BadRequestError({ message: 'You cannot edit secrets of a workspace you are not a member of' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
throw BadRequestError({ message: 'Unable to validate secrets' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return secrets;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface V1PushSecret {
|
interface V1PushSecret {
|
||||||
ciphertextKey: string;
|
ciphertextKey: string;
|
||||||
ivKey: string;
|
ivKey: string;
|
||||||
@@ -714,7 +662,6 @@ const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
validateSecrets,
|
|
||||||
v1PushSecrets,
|
v1PushSecrets,
|
||||||
v2PushSecrets,
|
v2PushSecrets,
|
||||||
pullSecrets,
|
pullSecrets,
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
User,
|
||||||
|
IUser,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceTokenData,
|
||||||
|
IServiceTokenData,
|
||||||
|
Secret,
|
||||||
|
ISecret
|
||||||
|
} from '../models';
|
||||||
|
import {
|
||||||
|
validateMembership
|
||||||
|
} from '../helpers/membership';
|
||||||
|
import {
|
||||||
|
validateUserClientForSecret,
|
||||||
|
validateUserClientForSecrets
|
||||||
|
} from '../helpers/user';
|
||||||
|
import {
|
||||||
|
validateServiceTokenDataClientForSecrets, validateServiceTokenDataClientForWorkspace
|
||||||
|
} from '../helpers/serviceTokenData';
|
||||||
|
import {
|
||||||
|
validateServiceAccountClientForSecrets,
|
||||||
|
validateServiceAccountClientForWorkspace
|
||||||
|
} from '../helpers/serviceAccount';
|
||||||
|
import {
|
||||||
|
BadRequestError,
|
||||||
|
UnauthorizedRequestError,
|
||||||
|
SecretNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for secrets with id [secretId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.secretId - id of secret to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateClientForSecret = async ({
|
||||||
|
authData,
|
||||||
|
secretId,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
},
|
||||||
|
secretId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
requiredPermissions: string[];
|
||||||
|
}) => {
|
||||||
|
const secret = await Secret.findById(secretId);
|
||||||
|
|
||||||
|
if (!secret) throw SecretNotFoundError({
|
||||||
|
message: 'Failed to find secret'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForSecret({
|
||||||
|
user: authData.authPayload,
|
||||||
|
secret,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: secret.workspace,
|
||||||
|
environment: secret.environment,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: authData.authPayload,
|
||||||
|
workspaceId: secret.workspace,
|
||||||
|
environment: secret.environment
|
||||||
|
});
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForSecret({
|
||||||
|
user: authData.authPayload,
|
||||||
|
secret,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for secret'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for secrets with ids [secretIds] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId[]} obj.secretIds - id of workspace to validate against
|
||||||
|
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateClientForSecrets = async ({
|
||||||
|
authData,
|
||||||
|
secretIds,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
},
|
||||||
|
secretIds: Types.ObjectId[];
|
||||||
|
requiredPermissions: string[];
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
let secrets: ISecret[] = [];
|
||||||
|
|
||||||
|
secrets = await Secret.find({
|
||||||
|
_id: {
|
||||||
|
$in: secretIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (secrets.length != secretIds.length) {
|
||||||
|
throw BadRequestError({ message: 'Failed to validate non-existent secrets' })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForSecrets({
|
||||||
|
user: authData.authPayload,
|
||||||
|
secrets,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForSecrets({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
secrets,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
await validateServiceTokenDataClientForSecrets({
|
||||||
|
serviceTokenData: authData.authPayload,
|
||||||
|
secrets,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForSecrets({
|
||||||
|
user: authData.authPayload,
|
||||||
|
secrets,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for secrets resource'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
validateClientForSecret,
|
||||||
|
validateClientForSecrets
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
User,
|
||||||
|
IUser,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceTokenData,
|
||||||
|
IServiceTokenData,
|
||||||
|
ISecret,
|
||||||
|
IOrganization,
|
||||||
|
IServiceAccountWorkspacePermission,
|
||||||
|
ServiceAccountWorkspacePermission
|
||||||
|
} from '../models';
|
||||||
|
import {
|
||||||
|
BadRequestError,
|
||||||
|
UnauthorizedRequestError,
|
||||||
|
ServiceAccountNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
PERMISSION_WRITE_SECRETS,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
import {
|
||||||
|
validateUserClientForServiceAccount
|
||||||
|
} from '../helpers/user';
|
||||||
|
|
||||||
|
const validateClientForServiceAccount = async ({
|
||||||
|
authData,
|
||||||
|
serviceAccountId,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
},
|
||||||
|
serviceAccountId: Types.ObjectId;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
|
||||||
|
|
||||||
|
if (!serviceAccount) {
|
||||||
|
throw ServiceAccountNotFoundError({
|
||||||
|
message: 'Failed to find service account'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForServiceAccount({
|
||||||
|
user: authData.authPayload,
|
||||||
|
serviceAccount,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForServiceAccount({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
targetServiceAccount: serviceAccount,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for service account resource'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForServiceAccount({
|
||||||
|
user: authData.authPayload,
|
||||||
|
serviceAccount,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for service account resource'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that service account (client) can access workspace
|
||||||
|
* with id [workspaceId] and its environment [environment] with required permissions
|
||||||
|
* [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {ServiceAccount} obj.serviceAccount - service account client
|
||||||
|
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||||
|
* @param {String} environment - (optional) environment in workspace to validate against
|
||||||
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateServiceAccountClientForWorkspace = async ({
|
||||||
|
serviceAccount,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
serviceAccount: IServiceAccount;
|
||||||
|
workspaceId: Types.ObjectId;
|
||||||
|
environment?: string;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
if (environment) {
|
||||||
|
// case: environment specified ->
|
||||||
|
// evaluate service account authorization for workspace
|
||||||
|
// in the context of a specific environment [environment]
|
||||||
|
const permission = await ServiceAccountWorkspacePermission.findOne({
|
||||||
|
serviceAccount,
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!permission) throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account authorization for the given workspace environment'
|
||||||
|
});
|
||||||
|
|
||||||
|
let runningIsDisallowed = false;
|
||||||
|
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||||
|
switch (requiredPermission) {
|
||||||
|
case PERMISSION_READ_SECRETS:
|
||||||
|
if (!permission.read) runningIsDisallowed = true;
|
||||||
|
break;
|
||||||
|
case PERMISSION_WRITE_SECRETS:
|
||||||
|
if (!permission.write) runningIsDisallowed = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningIsDisallowed) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// case: no environment specified ->
|
||||||
|
// evaluate service account authorization for workspace
|
||||||
|
// without need of environment [environment]
|
||||||
|
|
||||||
|
const permission = await ServiceAccountWorkspacePermission.findOne({
|
||||||
|
serviceAccount,
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!permission) throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account authorization for the given workspace'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that service account (client) can access secrets
|
||||||
|
* with required permissions [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {ServiceAccount} obj.serviceAccount - service account client
|
||||||
|
* @param {Secret[]} secrets - secrets to validate against
|
||||||
|
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateServiceAccountClientForSecrets = async ({
|
||||||
|
serviceAccount,
|
||||||
|
secrets,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
serviceAccount: IServiceAccount;
|
||||||
|
secrets: ISecret[];
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const permissions = await ServiceAccountWorkspacePermission.find({
|
||||||
|
serviceAccount: serviceAccount._id
|
||||||
|
});
|
||||||
|
|
||||||
|
const permissionsObj = _.keyBy(permissions, (p) => {
|
||||||
|
return `${p.workspace.toString()}-${p.environment}`
|
||||||
|
});
|
||||||
|
|
||||||
|
secrets.forEach((secret: ISecret) => {
|
||||||
|
const permission = permissionsObj[`${secret.workspace.toString()}-${secret.environment}`];
|
||||||
|
|
||||||
|
if (!permission) throw BadRequestError({
|
||||||
|
message: 'Failed to find any permission for the secret workspace and environment'
|
||||||
|
});
|
||||||
|
|
||||||
|
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||||
|
let runningIsDisallowed = false;
|
||||||
|
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||||
|
switch (requiredPermission) {
|
||||||
|
case PERMISSION_READ_SECRETS:
|
||||||
|
if (!permission.read) runningIsDisallowed = true;
|
||||||
|
break;
|
||||||
|
case PERMISSION_WRITE_SECRETS:
|
||||||
|
if (!permission.write) runningIsDisallowed = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningIsDisallowed) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that service account (client) can access target service
|
||||||
|
* account [serviceAccount] with required permissions [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {SerivceAccount} obj.serviceAccount - service account client
|
||||||
|
* @param {ServiceAccount} targetServiceAccount - target service account to validate against
|
||||||
|
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateServiceAccountClientForServiceAccount = ({
|
||||||
|
serviceAccount,
|
||||||
|
targetServiceAccount,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
serviceAccount: IServiceAccount;
|
||||||
|
targetServiceAccount: IServiceAccount;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
if (!serviceAccount.organization.equals(targetServiceAccount.organization)) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account authorization for the given service account'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that service account (client) can access organization [organization]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - service account client
|
||||||
|
* @param {Organization} obj.organization - organization to validate against
|
||||||
|
*/
|
||||||
|
const validateServiceAccountClientForOrganization = async ({
|
||||||
|
serviceAccount,
|
||||||
|
organization
|
||||||
|
}: {
|
||||||
|
serviceAccount: IServiceAccount;
|
||||||
|
organization: IOrganization;
|
||||||
|
}) => {
|
||||||
|
if (!serviceAccount.organization.equals(organization._id)) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service account authorization for the given organization'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
validateClientForServiceAccount,
|
||||||
|
validateServiceAccountClientForWorkspace,
|
||||||
|
validateServiceAccountClientForSecrets,
|
||||||
|
validateServiceAccountClientForServiceAccount,
|
||||||
|
validateServiceAccountClientForOrganization
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
ISecret,
|
||||||
|
IServiceTokenData,
|
||||||
|
ServiceTokenData,
|
||||||
|
IUser,
|
||||||
|
User,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccount,
|
||||||
|
} from '../models';
|
||||||
|
import {
|
||||||
|
UnauthorizedRequestError,
|
||||||
|
ServiceTokenDataNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
import { validateUserClientForWorkspace } from '../helpers/user';
|
||||||
|
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for service token with id [serviceTokenId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.serviceTokenData - id of service token to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
*/
|
||||||
|
const validateClientForServiceTokenData = async ({
|
||||||
|
authData,
|
||||||
|
serviceTokenDataId,
|
||||||
|
acceptedRoles
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
serviceTokenDataId: Types.ObjectId;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
}) => {
|
||||||
|
const serviceTokenData = await ServiceTokenData
|
||||||
|
.findById(serviceTokenDataId)
|
||||||
|
.select('+encryptedKey +iv +tag')
|
||||||
|
.populate<{ user: IUser }>('user');
|
||||||
|
|
||||||
|
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({
|
||||||
|
message: 'Failed to find service token data'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: serviceTokenData.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceTokenData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId: serviceTokenData.workspace
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceTokenData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for service token data'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId: serviceTokenData.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
return serviceTokenData;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for service token data'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that service token (client) can access workspace
|
||||||
|
* with id [workspaceId] and its environment [environment] with required permissions
|
||||||
|
* [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {ServiceTokenData} obj.serviceTokenData - service token client
|
||||||
|
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||||
|
* @param {String} environment - (optional) environment in workspace to validate against
|
||||||
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateServiceTokenDataClientForWorkspace = async ({
|
||||||
|
serviceTokenData,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
serviceTokenData: IServiceTokenData;
|
||||||
|
workspaceId: Types.ObjectId;
|
||||||
|
environment?: string;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
if (!serviceTokenData.workspace.equals(workspaceId)) {
|
||||||
|
// case: invalid workspaceId passed
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for the given workspace'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environment) {
|
||||||
|
// case: environment is specified
|
||||||
|
|
||||||
|
if (serviceTokenData.environment !== environment) {
|
||||||
|
// case: invalid environment passed
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for the given workspace environment'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
requiredPermissions?.forEach((permission) => {
|
||||||
|
if (!serviceTokenData.permissions.includes(permission)) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: `Failed service token authorization for the given workspace environment action: ${permission}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that service token (client) can access secrets
|
||||||
|
* with required permissions [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {ServiceTokenData} obj.serviceTokenData - service token client
|
||||||
|
* @param {Secret[]} secrets - secrets to validate against
|
||||||
|
* @param {string[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateServiceTokenDataClientForSecrets = async ({
|
||||||
|
serviceTokenData,
|
||||||
|
secrets,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
serviceTokenData: IServiceTokenData;
|
||||||
|
secrets: ISecret[];
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
secrets.forEach((secret: ISecret) => {
|
||||||
|
if (!serviceTokenData.workspace.equals(secret.workspace)) {
|
||||||
|
// case: invalid workspaceId passed
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for the given workspace'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serviceTokenData.environment !== secret.environment) {
|
||||||
|
// case: invalid environment passed
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed service token authorization for the given workspace environment'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
requiredPermissions?.forEach((permission) => {
|
||||||
|
if (!serviceTokenData.permissions.includes(permission)) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: `Failed service token authorization for the given workspace environment action: ${permission}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
validateClientForServiceTokenData,
|
||||||
|
validateServiceTokenDataClientForWorkspace,
|
||||||
|
validateServiceTokenDataClientForSecrets
|
||||||
|
}
|
||||||
+221
-2
@@ -1,6 +1,25 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { IUser, User } from '../models';
|
import { Types } from 'mongoose';
|
||||||
|
import {
|
||||||
|
IUser,
|
||||||
|
ISecret,
|
||||||
|
IServiceAccount,
|
||||||
|
User,
|
||||||
|
Membership,
|
||||||
|
IOrganization,
|
||||||
|
Organization,
|
||||||
|
} from '../models';
|
||||||
import { sendMail } from './nodemailer';
|
import { sendMail } from './nodemailer';
|
||||||
|
import { validateMembership } from './membership';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
import {
|
||||||
|
validateMembershipOrg
|
||||||
|
} from '../helpers/membershipOrg';
|
||||||
|
import {
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
PERMISSION_WRITE_SECRETS
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a user under email [email]
|
* Initialize a user under email [email]
|
||||||
@@ -146,4 +165,204 @@ const checkUserDevice = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { setupAccount, completeAccount, checkUserDevice };
|
/**
|
||||||
|
* Validate that user (client) can access workspace
|
||||||
|
* with id [workspaceId] and its environment [environment] with required permissions
|
||||||
|
* [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - user client
|
||||||
|
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||||
|
* @param {String} environment - (optional) environment in workspace to validate against
|
||||||
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateUserClientForWorkspace = async ({
|
||||||
|
user,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
user: IUser;
|
||||||
|
workspaceId: Types.ObjectId;
|
||||||
|
environment?: string;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
// validate user membership in workspace
|
||||||
|
const membership = await validateMembership({
|
||||||
|
userId: user._id,
|
||||||
|
workspaceId,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
let runningIsDisallowed = false;
|
||||||
|
requiredPermissions?.forEach((requiredPermission: string) => {
|
||||||
|
switch (requiredPermission) {
|
||||||
|
case PERMISSION_READ_SECRETS:
|
||||||
|
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
|
||||||
|
break;
|
||||||
|
case PERMISSION_WRITE_SECRETS:
|
||||||
|
runningIsDisallowed = _.some(membership.deniedPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningIsDisallowed) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: `Failed permissions authorization for workspace environment action : ${requiredPermission}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that user (client) can access secret [secret]
|
||||||
|
* with required permissions [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - user client
|
||||||
|
* @param {Secret[]} obj.secrets - secrets to validate against
|
||||||
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateUserClientForSecret = async ({
|
||||||
|
user,
|
||||||
|
secret,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
user: IUser;
|
||||||
|
secret: ISecret;
|
||||||
|
acceptedRoles?: Array<'admin' | 'member'>;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
const membership = await validateMembership({
|
||||||
|
userId: user._id,
|
||||||
|
workspaceId: secret.workspace,
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
|
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
|
||||||
|
const isDisallowed = _.some(membership.deniedPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
|
||||||
|
|
||||||
|
if (isDisallowed) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'You do not have the required permissions to perform this action'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that user (client) can access secrets [secrets]
|
||||||
|
* with required permissions [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - user client
|
||||||
|
* @param {Secret[]} obj.secrets - secrets to validate against
|
||||||
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateUserClientForSecrets = async ({
|
||||||
|
user,
|
||||||
|
secrets,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
user: IUser;
|
||||||
|
secrets: ISecret[];
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
// TODO: add acceptedRoles?
|
||||||
|
|
||||||
|
const userMemberships = await Membership.find({ user: user._id })
|
||||||
|
const userMembershipById = _.keyBy(userMemberships, 'workspace');
|
||||||
|
const workspaceIdsSet = new Set(userMemberships.map((m) => m.workspace.toString()));
|
||||||
|
|
||||||
|
// for each secret check if the secret belongs to a workspace the user is a member of
|
||||||
|
secrets.forEach((secret: ISecret) => {
|
||||||
|
if (!workspaceIdsSet.has(secret.workspace.toString())) {
|
||||||
|
throw BadRequestError({
|
||||||
|
message: 'Failed authorization for the secret'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredPermissions?.includes(PERMISSION_WRITE_SECRETS)) {
|
||||||
|
const deniedMembershipPermissions = userMembershipById[secret.workspace.toString()].deniedPermissions;
|
||||||
|
const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: secret.environment, ability: PERMISSION_WRITE_SECRETS });
|
||||||
|
|
||||||
|
if (isDisallowed) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'You do not have the required permissions to perform this action'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that user (client) can access service account [serviceAccount]
|
||||||
|
* with required permissions [requiredPermissions]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - user client
|
||||||
|
* @param {ServiceAccount} obj.serviceAccount - service account to validate against
|
||||||
|
* @param {String[]} requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateUserClientForServiceAccount = async ({
|
||||||
|
user,
|
||||||
|
serviceAccount,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
user: IUser;
|
||||||
|
serviceAccount: IServiceAccount;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
if (!serviceAccount.user.equals(user._id)) {
|
||||||
|
// case: user who created service account is not the
|
||||||
|
// same user that is on the request
|
||||||
|
await validateMembershipOrg({
|
||||||
|
userId: user._id,
|
||||||
|
organizationId: serviceAccount.organization,
|
||||||
|
acceptedRoles: [],
|
||||||
|
acceptedStatuses: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that user (client) can access organization [organization]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {User} obj.user - user client
|
||||||
|
* @param {Organization} obj.organization - organization to validate against
|
||||||
|
*/
|
||||||
|
const validateUserClientForOrganization = async ({
|
||||||
|
user,
|
||||||
|
organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
}: {
|
||||||
|
user: IUser;
|
||||||
|
organization: IOrganization;
|
||||||
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
|
}) => {
|
||||||
|
const membershipOrg = await validateMembershipOrg({
|
||||||
|
userId: user._id,
|
||||||
|
organizationId: organization._id,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
setupAccount,
|
||||||
|
completeAccount,
|
||||||
|
checkUserDevice,
|
||||||
|
validateUserClientForWorkspace,
|
||||||
|
validateUserClientForSecrets,
|
||||||
|
validateUserClientForServiceAccount,
|
||||||
|
validateUserClientForOrganization,
|
||||||
|
validateUserClientForSecret
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,12 +1,115 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
Workspace,
|
Workspace,
|
||||||
Bot,
|
Bot,
|
||||||
Membership,
|
Membership,
|
||||||
Key,
|
Key,
|
||||||
Secret
|
Secret,
|
||||||
|
User,
|
||||||
|
IUser,
|
||||||
|
ServiceAccountWorkspacePermission,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceTokenData,
|
||||||
|
IServiceTokenData,
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { createBot } from '../helpers/bot';
|
import { createBot } from '../helpers/bot';
|
||||||
|
import { validateUserClientForWorkspace } from '../helpers/user';
|
||||||
|
import { validateServiceAccountClientForWorkspace } from '../helpers/serviceAccount';
|
||||||
|
import { validateServiceTokenDataClientForWorkspace } from '../helpers/serviceTokenData';
|
||||||
|
import { validateMembership } from '../helpers/membership';
|
||||||
|
import { UnauthorizedRequestError, WorkspaceNotFoundError } from '../utils/errors';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate authenticated clients for workspace with id [workspaceId] based
|
||||||
|
* on any known permissions.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {Object} obj.authData - authenticated client details
|
||||||
|
* @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against
|
||||||
|
* @param {String} obj.environment - (optional) environment in workspace to validate against
|
||||||
|
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
|
||||||
|
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||||
|
*/
|
||||||
|
const validateClientForWorkspace = async ({
|
||||||
|
authData,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
}: {
|
||||||
|
authData: {
|
||||||
|
authMode: string;
|
||||||
|
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
|
};
|
||||||
|
workspaceId: Types.ObjectId;
|
||||||
|
environment?: string;
|
||||||
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const workspace = await Workspace.findById(workspaceId);
|
||||||
|
|
||||||
|
if (!workspace) throw WorkspaceNotFoundError({
|
||||||
|
message: 'Failed to find workspace'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||||
|
const membership = await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ membership });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
|
await validateServiceAccountClientForWorkspace({
|
||||||
|
serviceAccount: authData.authPayload,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: authData.authPayload,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
|
||||||
|
const membership = await validateUserClientForWorkspace({
|
||||||
|
user: authData.authPayload,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return ({ membership });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: 'Failed client authorization for workspace'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a workspace with name [name] in organization with id [organizationId]
|
* Create a workspace with name [name] in organization with id [organizationId]
|
||||||
@@ -71,4 +174,8 @@ const deleteWorkspace = async ({ id }: { id: string }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { createWorkspace, deleteWorkspace };
|
export {
|
||||||
|
validateClientForWorkspace,
|
||||||
|
createWorkspace,
|
||||||
|
deleteWorkspace
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as Sentry from '@sentry/node';
|
|||||||
import { DatabaseService } from './services';
|
import { DatabaseService } from './services';
|
||||||
import { setUpHealthEndpoint } from './services/health';
|
import { setUpHealthEndpoint } from './services/health';
|
||||||
import { initSmtp } from './services/smtp';
|
import { initSmtp } from './services/smtp';
|
||||||
import { logTelemetryMessage } from './services';
|
import { TelemetryService } from './services';
|
||||||
import { setTransporter } from './helpers/nodemailer';
|
import { setTransporter } from './helpers/nodemailer';
|
||||||
import { createTestUserForDevelopment } from './utils/addDevelopmentUser';
|
import { createTestUserForDevelopment } from './utils/addDevelopmentUser';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
@@ -56,6 +56,7 @@ import {
|
|||||||
secret as v2SecretRouter, // begin to phase out
|
secret as v2SecretRouter, // begin to phase out
|
||||||
secrets as v2SecretsRouter,
|
secrets as v2SecretsRouter,
|
||||||
serviceTokenData as v2ServiceTokenDataRouter,
|
serviceTokenData as v2ServiceTokenDataRouter,
|
||||||
|
serviceAccounts as v2ServiceAccountsRouter,
|
||||||
apiKeyData as v2APIKeyDataRouter,
|
apiKeyData as v2APIKeyDataRouter,
|
||||||
environment as v2EnvironmentRouter,
|
environment as v2EnvironmentRouter,
|
||||||
tags as v2TagsRouter,
|
tags as v2TagsRouter,
|
||||||
@@ -79,7 +80,7 @@ const main = async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logTelemetryMessage();
|
TelemetryService.logTelemetryMessage();
|
||||||
setTransporter(initSmtp());
|
setTransporter(initSmtp());
|
||||||
|
|
||||||
await DatabaseService.initDatabase(getMongoURL());
|
await DatabaseService.initDatabase(getMongoURL());
|
||||||
@@ -150,6 +151,7 @@ const main = async () => {
|
|||||||
app.use('/api/v2/secret', v2SecretRouter); // deprecated
|
app.use('/api/v2/secret', v2SecretRouter); // deprecated
|
||||||
app.use('/api/v2/secrets', v2SecretsRouter);
|
app.use('/api/v2/secrets', v2SecretsRouter);
|
||||||
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
|
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
|
||||||
|
app.use('/api/v2/service-accounts', v2ServiceAccountsRouter); // new
|
||||||
app.use('/api/v2/api-key', v2APIKeyDataRouter);
|
app.use('/api/v2/api-key', v2APIKeyDataRouter);
|
||||||
|
|
||||||
// api docs
|
// api docs
|
||||||
@@ -170,7 +172,7 @@ const main = async () => {
|
|||||||
getLogger("backend-main").info(`Server started listening at port ${getPort()}`)
|
getLogger("backend-main").info(`Server started listening at port ${getPort()}`)
|
||||||
});
|
});
|
||||||
|
|
||||||
createTestUserForDevelopment();
|
await createTestUserForDevelopment();
|
||||||
setUpHealthEndpoint(server);
|
setUpHealthEndpoint(server);
|
||||||
|
|
||||||
server.on('close', async () => {
|
server.on('close', async () => {
|
||||||
|
|||||||
@@ -12,17 +12,21 @@ import {
|
|||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
INTEGRATION_RENDER,
|
INTEGRATION_RENDER,
|
||||||
|
INTEGRATION_RAILWAY,
|
||||||
INTEGRATION_FLYIO,
|
INTEGRATION_FLYIO,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
INTEGRATION_TRAVISCI,
|
INTEGRATION_TRAVISCI,
|
||||||
|
INTEGRATION_SUPABASE,
|
||||||
INTEGRATION_HEROKU_API_URL,
|
INTEGRATION_HEROKU_API_URL,
|
||||||
INTEGRATION_GITLAB_API_URL,
|
INTEGRATION_GITLAB_API_URL,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_VERCEL_API_URL,
|
||||||
INTEGRATION_NETLIFY_API_URL,
|
INTEGRATION_NETLIFY_API_URL,
|
||||||
INTEGRATION_RENDER_API_URL,
|
INTEGRATION_RENDER_API_URL,
|
||||||
|
INTEGRATION_RAILWAY_API_URL,
|
||||||
INTEGRATION_FLYIO_API_URL,
|
INTEGRATION_FLYIO_API_URL,
|
||||||
INTEGRATION_CIRCLECI_API_URL,
|
INTEGRATION_CIRCLECI_API_URL,
|
||||||
INTEGRATION_TRAVISCI_API_URL,
|
INTEGRATION_TRAVISCI_API_URL,
|
||||||
|
INTEGRATION_SUPABASE_API_URL
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
|
|
||||||
interface App {
|
interface App {
|
||||||
@@ -94,6 +98,11 @@ const getApps = async ({
|
|||||||
accessToken,
|
accessToken,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case INTEGRATION_RAILWAY:
|
||||||
|
apps = await getAppsRailway({
|
||||||
|
accessToken
|
||||||
|
});
|
||||||
|
break;
|
||||||
case INTEGRATION_FLYIO:
|
case INTEGRATION_FLYIO:
|
||||||
apps = await getAppsFlyio({
|
apps = await getAppsFlyio({
|
||||||
accessToken,
|
accessToken,
|
||||||
@@ -109,6 +118,11 @@ const getApps = async ({
|
|||||||
accessToken,
|
accessToken,
|
||||||
})
|
})
|
||||||
break;
|
break;
|
||||||
|
case INTEGRATION_SUPABASE:
|
||||||
|
apps = await getAppsSupabase({
|
||||||
|
accessToken
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
@@ -184,6 +198,7 @@ const getAppsVercel = async ({
|
|||||||
|
|
||||||
apps = res.projects.map((a: any) => ({
|
apps = res.projects.map((a: any) => ({
|
||||||
name: a.name,
|
name: a.name,
|
||||||
|
appId: a.id
|
||||||
}));
|
}));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
@@ -270,10 +285,13 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
|
|
||||||
apps = repos
|
apps = repos
|
||||||
.filter((a: any) => a.permissions.admin === true)
|
.filter((a: any) => a.permissions.admin === true)
|
||||||
.map((a: any) => ({
|
.map((a: any) => {
|
||||||
name: a.name,
|
return ({
|
||||||
owner: a.owner.login,
|
appId: a.id,
|
||||||
}));
|
name: a.name,
|
||||||
|
owner: a.owner.login,
|
||||||
|
});
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
@@ -319,6 +337,58 @@ const getAppsRender = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
return apps;
|
return apps;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of projects for Railway integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.accessToken - access token for Railway API
|
||||||
|
* @returns {Object[]} apps - names and ids of Railway services
|
||||||
|
* @returns {String} apps.name - name of Railway project
|
||||||
|
* @returns {String} apps.appId - id of Railway project
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const getAppsRailway = async ({ accessToken }: { accessToken: string }) => {
|
||||||
|
let apps: any[] = [];
|
||||||
|
try {
|
||||||
|
const query = `
|
||||||
|
query GetProjects($userId: String, $teamId: String) {
|
||||||
|
projects(userId: $userId, teamId: $teamId) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const variables = {};
|
||||||
|
|
||||||
|
const { data: { data: { projects: { edges }}} } = await request.post(INTEGRATION_RAILWAY_API_URL, {
|
||||||
|
query,
|
||||||
|
variables,
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept-Encoding': 'application/json'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
apps = edges.map((e: any) => ({
|
||||||
|
name: e.node.name,
|
||||||
|
appId: e.node.id
|
||||||
|
}));
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error("Failed to get Railway services");
|
||||||
|
}
|
||||||
|
|
||||||
|
return apps;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of apps for Fly.io integration
|
* Return list of apps for Fly.io integration
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
@@ -545,4 +615,40 @@ const getAppsGitlab = async ({
|
|||||||
return apps;
|
return apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of projects for Supabase integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.accessToken - access token for Supabase API
|
||||||
|
* @returns {Object[]} apps - names of Supabase apps
|
||||||
|
* @returns {String} apps.name - name of Supabase app
|
||||||
|
*/
|
||||||
|
const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => {
|
||||||
|
let apps: any;
|
||||||
|
try {
|
||||||
|
const { data } = await request.get(
|
||||||
|
`${INTEGRATION_SUPABASE_API_URL}/v1/projects`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
'Accept-Encoding': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
apps = data.map((a: any) => {
|
||||||
|
return {
|
||||||
|
name: a.name,
|
||||||
|
appId: a.id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to get Supabase projects');
|
||||||
|
}
|
||||||
|
|
||||||
|
return apps;
|
||||||
|
};
|
||||||
|
|
||||||
export { getApps };
|
export { getApps };
|
||||||
|
|||||||
@@ -21,19 +21,24 @@ import {
|
|||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
INTEGRATION_RENDER,
|
INTEGRATION_RENDER,
|
||||||
|
INTEGRATION_RAILWAY,
|
||||||
INTEGRATION_FLYIO,
|
INTEGRATION_FLYIO,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
INTEGRATION_TRAVISCI,
|
INTEGRATION_TRAVISCI,
|
||||||
|
INTEGRATION_SUPABASE,
|
||||||
INTEGRATION_HEROKU_API_URL,
|
INTEGRATION_HEROKU_API_URL,
|
||||||
INTEGRATION_GITLAB_API_URL,
|
INTEGRATION_GITLAB_API_URL,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
INTEGRATION_VERCEL_API_URL,
|
||||||
INTEGRATION_NETLIFY_API_URL,
|
INTEGRATION_NETLIFY_API_URL,
|
||||||
INTEGRATION_RENDER_API_URL,
|
INTEGRATION_RENDER_API_URL,
|
||||||
|
INTEGRATION_RAILWAY_API_URL,
|
||||||
INTEGRATION_FLYIO_API_URL,
|
INTEGRATION_FLYIO_API_URL,
|
||||||
INTEGRATION_CIRCLECI_API_URL,
|
INTEGRATION_CIRCLECI_API_URL,
|
||||||
INTEGRATION_TRAVISCI_API_URL,
|
INTEGRATION_TRAVISCI_API_URL,
|
||||||
|
INTEGRATION_SUPABASE_API_URL
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
import request from '../config/request';
|
import request from '../config/request';
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync/push [secrets] to [app] in integration named [integration]
|
* Sync/push [secrets] to [app] in integration named [integration]
|
||||||
@@ -126,6 +131,13 @@ const syncSecrets = async ({
|
|||||||
accessToken,
|
accessToken,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case INTEGRATION_RAILWAY:
|
||||||
|
await syncSecretsRailway({
|
||||||
|
integration,
|
||||||
|
secrets,
|
||||||
|
accessToken
|
||||||
|
});
|
||||||
|
break;
|
||||||
case INTEGRATION_FLYIO:
|
case INTEGRATION_FLYIO:
|
||||||
await syncSecretsFlyio({
|
await syncSecretsFlyio({
|
||||||
integration,
|
integration,
|
||||||
@@ -147,6 +159,13 @@ const syncSecrets = async ({
|
|||||||
accessToken,
|
accessToken,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case INTEGRATION_SUPABASE:
|
||||||
|
await syncSecretsSupabase({
|
||||||
|
integration,
|
||||||
|
secrets,
|
||||||
|
accessToken
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
@@ -608,6 +627,7 @@ const syncSecretsVercel = async ({
|
|||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
target: string[];
|
target: string[];
|
||||||
|
gitBranch?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -622,45 +642,6 @@ const syncSecretsVercel = async ({
|
|||||||
: {}),
|
: {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
// const res = (
|
|
||||||
// await Promise.all(
|
|
||||||
// (
|
|
||||||
// await request.get(
|
|
||||||
// `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`,
|
|
||||||
// {
|
|
||||||
// params,
|
|
||||||
// headers: {
|
|
||||||
// Authorization: `Bearer ${accessToken}`,
|
|
||||||
// 'Accept-Encoding': 'application/json'
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ))
|
|
||||||
// .data
|
|
||||||
// .envs
|
|
||||||
// .filter((secret: VercelSecret) => secret.target.includes(integration.targetEnvironment))
|
|
||||||
// .map(async (secret: VercelSecret) => {
|
|
||||||
// if (secret.type === 'encrypted') {
|
|
||||||
// // case: secret is encrypted -> need to decrypt
|
|
||||||
// const decryptedSecret = (await request.get(
|
|
||||||
// `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`,
|
|
||||||
// {
|
|
||||||
// params,
|
|
||||||
// headers: {
|
|
||||||
// Authorization: `Bearer ${accessToken}`,
|
|
||||||
// 'Accept-Encoding': 'application/json'
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// )).data;
|
|
||||||
|
|
||||||
// return decryptedSecret;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return secret;
|
|
||||||
// }))).reduce((obj: any, secret: any) => ({
|
|
||||||
// ...obj,
|
|
||||||
// [secret.key]: secret
|
|
||||||
// }), {});
|
|
||||||
|
|
||||||
const vercelSecrets: VercelSecret[] = (await request.get(
|
const vercelSecrets: VercelSecret[] = (await request.get(
|
||||||
`${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`,
|
`${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`,
|
||||||
{
|
{
|
||||||
@@ -673,7 +654,21 @@ const syncSecretsVercel = async ({
|
|||||||
))
|
))
|
||||||
.data
|
.data
|
||||||
.envs
|
.envs
|
||||||
.filter((secret: VercelSecret) => secret.target.includes(integration.targetEnvironment));
|
.filter((secret: VercelSecret) => {
|
||||||
|
if (!secret.target.includes(integration.targetEnvironment)) {
|
||||||
|
// case: secret does not have the same target environment
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (integration.targetEnvironment === 'preview' && integration.path && integration.path !== secret.gitBranch) {
|
||||||
|
// case: secret on preview environment does not have same target git branch
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// return secret.target.includes(integration.targetEnvironment);
|
||||||
|
|
||||||
const res: { [key: string]: VercelSecret } = {};
|
const res: { [key: string]: VercelSecret } = {};
|
||||||
|
|
||||||
@@ -710,6 +705,9 @@ const syncSecretsVercel = async ({
|
|||||||
value: secrets[key],
|
value: secrets[key],
|
||||||
type: "encrypted",
|
type: "encrypted",
|
||||||
target: [integration.targetEnvironment],
|
target: [integration.targetEnvironment],
|
||||||
|
...(integration.path ? {
|
||||||
|
gitBranch: integration.path
|
||||||
|
} : {})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -726,7 +724,10 @@ const syncSecretsVercel = async ({
|
|||||||
type: res[key].type,
|
type: res[key].type,
|
||||||
target: res[key].target.includes(integration.targetEnvironment)
|
target: res[key].target.includes(integration.targetEnvironment)
|
||||||
? [...res[key].target]
|
? [...res[key].target]
|
||||||
: [...res[key].target, integration.targetEnvironment]
|
: [...res[key].target, integration.targetEnvironment],
|
||||||
|
...(integration.path ? {
|
||||||
|
gitBranch: integration.path
|
||||||
|
} : {})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -737,6 +738,9 @@ const syncSecretsVercel = async ({
|
|||||||
value: res[key].value,
|
value: res[key].value,
|
||||||
type: "encrypted", // value doesn't matter
|
type: "encrypted", // value doesn't matter
|
||||||
target: [integration.targetEnvironment],
|
target: [integration.targetEnvironment],
|
||||||
|
...(integration.path ? {
|
||||||
|
gitBranch: integration.path
|
||||||
|
} : {})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1060,7 +1064,7 @@ const syncSecretsGitHub = async ({
|
|||||||
"GET /repos/{owner}/{repo}/actions/secrets/public-key",
|
"GET /repos/{owner}/{repo}/actions/secrets/public-key",
|
||||||
{
|
{
|
||||||
owner: integration.owner,
|
owner: integration.owner,
|
||||||
repo: integration.app,
|
repo: integration.app
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
).data;
|
).data;
|
||||||
@@ -1167,6 +1171,58 @@ const syncSecretsRender = async ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync/push [secrets] to Railway project with id [integration.appId]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {IIntegration} obj.integration - integration details
|
||||||
|
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||||
|
* @param {String} obj.accessToken - access token for Railway integration
|
||||||
|
*/
|
||||||
|
const syncSecretsRailway = async ({
|
||||||
|
integration,
|
||||||
|
secrets,
|
||||||
|
accessToken
|
||||||
|
}: {
|
||||||
|
integration: IIntegration;
|
||||||
|
secrets: any;
|
||||||
|
accessToken: string;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
mutation UpsertVariables($input: VariableCollectionUpsertInput!) {
|
||||||
|
variableCollectionUpsert(input: $input)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
projectId: integration.appId,
|
||||||
|
environmentId: integration.targetEnvironmentId,
|
||||||
|
...(integration.targetServiceId ? { serviceId: integration.targetServiceId } : {}),
|
||||||
|
replace: true,
|
||||||
|
variables: secrets
|
||||||
|
};
|
||||||
|
|
||||||
|
await request.post(INTEGRATION_RAILWAY_API_URL, {
|
||||||
|
query,
|
||||||
|
variables: {
|
||||||
|
input,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept-Encoding': 'application/json'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error("Failed to sync secrets to Railway");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync/push [secrets] to Fly.io app
|
* Sync/push [secrets] to Fly.io app
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
@@ -1571,4 +1627,79 @@ const syncSecretsGitLab = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync/push [secrets] to Supabase with name [integration.app]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {IIntegration} obj.integration - integration details
|
||||||
|
* @param {IIntegrationAuth} obj.integrationAuth - integration auth details
|
||||||
|
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||||
|
* @param {String} obj.accessToken - access token for Supabase integration
|
||||||
|
*/
|
||||||
|
const syncSecretsSupabase = async ({
|
||||||
|
integration,
|
||||||
|
secrets,
|
||||||
|
accessToken
|
||||||
|
}: {
|
||||||
|
integration: IIntegration;
|
||||||
|
secrets: any;
|
||||||
|
accessToken: string;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
const { data: getSecretsRes } = await request.get(
|
||||||
|
`${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
'Accept-Encoding': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// convert the secrets to [{}] format
|
||||||
|
const modifiedFormatForSecretInjection = Object.keys(secrets).map(
|
||||||
|
(key) => {
|
||||||
|
return {
|
||||||
|
name: key,
|
||||||
|
value: secrets[key]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await request.post(
|
||||||
|
`${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`,
|
||||||
|
modifiedFormatForSecretInjection,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
'Accept-Encoding': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const secretsToDelete: any = [];
|
||||||
|
getSecretsRes?.forEach((secretObj: any) => {
|
||||||
|
if (!(secretObj.name in secrets)) {
|
||||||
|
secretsToDelete.push(secretObj.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await request.delete(
|
||||||
|
`${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept-Encoding': 'application/json'
|
||||||
|
},
|
||||||
|
data: secretsToDelete
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to sync secrets to Supabase');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export { syncSecrets };
|
export { syncSecrets };
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
interface AddServiceAccountPermissionDto {
|
||||||
|
name: string;
|
||||||
|
workspaceId?: string;
|
||||||
|
environment?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddServiceAccountPermissionDto;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
interface CreateServiceAccountDto {
|
||||||
|
organizationId: string;
|
||||||
|
name: string;
|
||||||
|
publicKey: string;
|
||||||
|
expiresIn: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateServiceAccountDto;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import CreateServiceAccountDto from './CreateServiceAccountDto';
|
||||||
|
import AddServiceAccountPermissionDto from './AddServiceAccountPermissionDto';
|
||||||
|
|
||||||
|
export {
|
||||||
|
CreateServiceAccountDto,
|
||||||
|
AddServiceAccountPermissionDto
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ import requireIntegrationAuth from './requireIntegrationAuth';
|
|||||||
import requireIntegrationAuthorizationAuth from './requireIntegrationAuthorizationAuth';
|
import requireIntegrationAuthorizationAuth from './requireIntegrationAuthorizationAuth';
|
||||||
import requireServiceTokenAuth from './requireServiceTokenAuth';
|
import requireServiceTokenAuth from './requireServiceTokenAuth';
|
||||||
import requireServiceTokenDataAuth from './requireServiceTokenDataAuth';
|
import requireServiceTokenDataAuth from './requireServiceTokenDataAuth';
|
||||||
|
import requireServiceAccountAuth from './requireServiceAccountAuth';
|
||||||
|
import requireServiceAccountWorkspacePermissionAuth from './requireServiceAccountWorkspacePermissionAuth';
|
||||||
import requireSecretAuth from './requireSecretAuth';
|
import requireSecretAuth from './requireSecretAuth';
|
||||||
import requireSecretsAuth from './requireSecretsAuth';
|
import requireSecretsAuth from './requireSecretsAuth';
|
||||||
import validateRequest from './validateRequest';
|
import validateRequest from './validateRequest';
|
||||||
@@ -27,6 +29,8 @@ export {
|
|||||||
requireIntegrationAuthorizationAuth,
|
requireIntegrationAuthorizationAuth,
|
||||||
requireServiceTokenAuth,
|
requireServiceTokenAuth,
|
||||||
requireServiceTokenDataAuth,
|
requireServiceTokenDataAuth,
|
||||||
|
requireServiceAccountAuth,
|
||||||
|
requireServiceAccountWorkspacePermissionAuth,
|
||||||
requireSecretAuth,
|
requireSecretAuth,
|
||||||
requireSecretsAuth,
|
requireSecretsAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
|
|||||||
@@ -4,11 +4,23 @@ import {
|
|||||||
validateAuthMode,
|
validateAuthMode,
|
||||||
getAuthUserPayload,
|
getAuthUserPayload,
|
||||||
getAuthSTDPayload,
|
getAuthSTDPayload,
|
||||||
getAuthAPIKeyPayload
|
getAuthAPIKeyPayload,
|
||||||
|
getAuthSAAKPayload
|
||||||
} from '../helpers/auth';
|
} from '../helpers/auth';
|
||||||
import {
|
import {
|
||||||
UnauthorizedRequestError
|
UnauthorizedRequestError
|
||||||
} from '../utils/errors';
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
IUser,
|
||||||
|
IServiceAccount,
|
||||||
|
IServiceTokenData
|
||||||
|
} from '../models';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../variables';
|
||||||
|
|
||||||
declare module 'jsonwebtoken' {
|
declare module 'jsonwebtoken' {
|
||||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||||
@@ -27,50 +39,58 @@ declare module 'jsonwebtoken' {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const requireAuth = ({
|
const requireAuth = ({
|
||||||
acceptedAuthModes = ['jwt'],
|
acceptedAuthModes = [AUTH_MODE_JWT],
|
||||||
requiredServiceTokenPermissions = []
|
|
||||||
}: {
|
}: {
|
||||||
acceptedAuthModes: string[];
|
acceptedAuthModes: string[];
|
||||||
requiredServiceTokenPermissions?: string[];
|
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
|
||||||
// validate auth token against accepted auth modes [acceptedAuthModes]
|
// validate auth token against accepted auth modes [acceptedAuthModes]
|
||||||
// and return token type [authTokenType] and value [authTokenValue]
|
// and return token type [authTokenType] and value [authTokenValue]
|
||||||
const { authTokenType, authTokenValue } = validateAuthMode({
|
const { authMode, authTokenValue } = validateAuthMode({
|
||||||
headers: req.headers,
|
headers: req.headers,
|
||||||
acceptedAuthModes
|
acceptedAuthModes
|
||||||
});
|
});
|
||||||
|
|
||||||
// attach auth payloads
|
let authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||||
let serviceTokenData: any;
|
switch (authMode) {
|
||||||
switch (authTokenType) {
|
case AUTH_MODE_SERVICE_ACCOUNT:
|
||||||
case 'serviceToken':
|
authPayload = await getAuthSAAKPayload({
|
||||||
serviceTokenData = await getAuthSTDPayload({
|
|
||||||
authTokenValue
|
authTokenValue
|
||||||
});
|
});
|
||||||
|
req.serviceAccount = authPayload;
|
||||||
requiredServiceTokenPermissions.forEach((requiredServiceTokenPermission) => {
|
|
||||||
if (!serviceTokenData.permissions.includes(requiredServiceTokenPermission)) {
|
|
||||||
return next(UnauthorizedRequestError({ message: 'Failed to authorize service token for endpoint' }));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
req.serviceTokenData = serviceTokenData;
|
|
||||||
req.user = serviceTokenData?.user;
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'apiKey':
|
case AUTH_MODE_SERVICE_TOKEN:
|
||||||
req.user = await getAuthAPIKeyPayload({
|
authPayload = await getAuthSTDPayload({
|
||||||
authTokenValue
|
authTokenValue
|
||||||
});
|
});
|
||||||
|
req.serviceTokenData = authPayload;
|
||||||
|
break;
|
||||||
|
case AUTH_MODE_API_KEY:
|
||||||
|
authPayload = await getAuthAPIKeyPayload({
|
||||||
|
authTokenValue
|
||||||
|
});
|
||||||
|
req.user = authPayload;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
req.user = await getAuthUserPayload({
|
authPayload = await getAuthUserPayload({
|
||||||
authTokenValue
|
authTokenValue
|
||||||
});
|
});
|
||||||
|
req.user = authPayload;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.requestData = {
|
||||||
|
...req.params,
|
||||||
|
...req.query,
|
||||||
|
...req.body,
|
||||||
|
}
|
||||||
|
|
||||||
|
req.authData = {
|
||||||
|
authMode,
|
||||||
|
authPayload // User, ServiceAccount, ServiceTokenData
|
||||||
|
}
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,28 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { Bot } from '../models';
|
import { Bot } from '../models';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
|
import { validateClientForBot } from '../helpers/bot';
|
||||||
import { AccountNotFoundError } from '../utils/errors';
|
import { AccountNotFoundError } from '../utils/errors';
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
const requireBotAuth = ({
|
const requireBotAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
location = 'params'
|
locationBotId = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
location?: req;
|
locationBotId?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const bot = await Bot.findById(req[location].botId);
|
const { botId } = req[locationBotId];
|
||||||
|
|
||||||
if (!bot) {
|
req.bot = await validateClientForBot({
|
||||||
return next(AccountNotFoundError({message: 'Failed to locate Bot account'}))
|
authData: req.authData,
|
||||||
}
|
botId: new Types.ObjectId(botId),
|
||||||
|
|
||||||
await validateMembership({
|
|
||||||
userId: req.user._id.toString(),
|
|
||||||
workspaceId: bot.workspace.toString(),
|
|
||||||
acceptedRoles
|
acceptedRoles
|
||||||
});
|
});
|
||||||
|
|
||||||
req.bot = bot;
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { Integration, IntegrationAuth } from '../models';
|
import { Integration, IntegrationAuth } from '../models';
|
||||||
import { IntegrationService } from '../services';
|
import { IntegrationService } from '../services';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
|
import { validateClientForIntegration } from '../helpers/integration';
|
||||||
import { IntegrationNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
import { IntegrationNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,42 +15,24 @@ import { IntegrationNotFoundError, UnauthorizedRequestError } from '../utils/err
|
|||||||
const requireIntegrationAuth = ({
|
const requireIntegrationAuth = ({
|
||||||
acceptedRoles
|
acceptedRoles
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
// integration authorization middleware
|
|
||||||
|
|
||||||
const { integrationId } = req.params;
|
const { integrationId } = req.params;
|
||||||
|
|
||||||
// validate integration accessibility
|
const { integration, accessToken } = await validateClientForIntegration({
|
||||||
const integration = await Integration.findOne({
|
authData: req.authData,
|
||||||
_id: integrationId
|
integrationId: new Types.ObjectId(integrationId),
|
||||||
});
|
|
||||||
|
|
||||||
if (!integration) {
|
|
||||||
return next(IntegrationNotFoundError({message: 'Failed to locate Integration'}))
|
|
||||||
}
|
|
||||||
|
|
||||||
await validateMembership({
|
|
||||||
userId: req.user._id.toString(),
|
|
||||||
workspaceId: integration.workspace.toString(),
|
|
||||||
acceptedRoles
|
acceptedRoles
|
||||||
});
|
});
|
||||||
|
|
||||||
const integrationAuth = await IntegrationAuth.findOne({
|
if (integration) {
|
||||||
_id: integration.integrationAuth
|
req.integration = integration;
|
||||||
}).select(
|
|
||||||
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!integrationAuth) {
|
|
||||||
return next(UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req.integration = integration;
|
if (accessToken) {
|
||||||
req.accessToken = await IntegrationService.getIntegrationAuthAccess({
|
req.accessToken = accessToken;
|
||||||
integrationAuthId: integrationAuth._id.toString()
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { IntegrationAuth, IWorkspace } from '../models';
|
import { IntegrationAuth, IWorkspace } from '../models';
|
||||||
import { IntegrationService } from '../services';
|
import { IntegrationService } from '../services';
|
||||||
|
import { validateClientForIntegrationAuth } from '../helpers/integrationAuth';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
@@ -19,36 +21,26 @@ const requireIntegrationAuthorizationAuth = ({
|
|||||||
attachAccessToken = true,
|
attachAccessToken = true,
|
||||||
location = 'params'
|
location = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
attachAccessToken?: boolean;
|
attachAccessToken?: boolean;
|
||||||
location?: req;
|
location?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const { integrationAuthId } = req[location];
|
const { integrationAuthId } = req[location];
|
||||||
const integrationAuth = await IntegrationAuth.findOne({
|
|
||||||
_id: integrationAuthId
|
|
||||||
})
|
|
||||||
.populate<{ workspace: IWorkspace }>('workspace')
|
|
||||||
.select(
|
|
||||||
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!integrationAuth) {
|
const { integrationAuth, accessToken } = await validateClientForIntegrationAuth({
|
||||||
return next(UnauthorizedRequestError({message: 'Failed to locate Integration Authorization credentials'}))
|
authData: req.authData,
|
||||||
}
|
integrationAuthId: new Types.ObjectId(integrationAuthId),
|
||||||
|
acceptedRoles,
|
||||||
await validateMembership({
|
attachAccessToken
|
||||||
userId: req.user._id.toString(),
|
|
||||||
workspaceId: integrationAuth.workspace._id.toString(),
|
|
||||||
acceptedRoles
|
|
||||||
});
|
});
|
||||||
|
|
||||||
req.integrationAuth = integrationAuth;
|
if (integrationAuth) {
|
||||||
if (attachAccessToken) {
|
req.integrationAuth = integrationAuth;
|
||||||
const access = await IntegrationService.getIntegrationAuthAccess({
|
}
|
||||||
integrationAuthId: integrationAuth._id.toString()
|
|
||||||
});
|
if (accessToken) {
|
||||||
req.accessToken = access.accessToken;
|
req.accessToken = accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
import {
|
import {
|
||||||
Membership,
|
Membership,
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import {
|
||||||
|
validateClientForMembership,
|
||||||
|
validateMembership
|
||||||
|
} from '../helpers/membership';
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
@@ -16,43 +20,25 @@ type req = 'params' | 'body' | 'query';
|
|||||||
*/
|
*/
|
||||||
const requireMembershipAuth = ({
|
const requireMembershipAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
location = 'params'
|
locationMembershipId = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
location?: req;
|
locationMembershipId: req
|
||||||
}) => {
|
}) => {
|
||||||
return async (
|
return async (
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
) => {
|
) => {
|
||||||
try {
|
const { membershipId } = req[locationMembershipId];
|
||||||
const { membershipId } = req[location];
|
|
||||||
|
|
||||||
const membership = await Membership.findById(membershipId);
|
req.targetMembership = await validateClientForMembership({
|
||||||
|
authData: req.authData,
|
||||||
|
membershipId: new Types.ObjectId(membershipId),
|
||||||
|
acceptedRoles
|
||||||
|
});
|
||||||
|
|
||||||
if (!membership) throw new Error('Failed to find target membership');
|
return next();
|
||||||
|
|
||||||
const userMembership = await Membership.findOne({
|
|
||||||
workspace: membership.workspace
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!userMembership) throw new Error('Failed to validate own membership')
|
|
||||||
|
|
||||||
const targetMembership = await validateMembership({
|
|
||||||
userId: req.user._id.toString(),
|
|
||||||
workspaceId: membership.workspace.toString(),
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
|
|
||||||
req.targetMembership = targetMembership;
|
|
||||||
|
|
||||||
return next();
|
|
||||||
} catch (err) {
|
|
||||||
return next(UnauthorizedRequestError({
|
|
||||||
message: 'Unable to validate workspace membership'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
import {
|
import {
|
||||||
MembershipOrg
|
MembershipOrg
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import { validateMembership } from '../helpers/membershipOrg';
|
import {
|
||||||
|
validateClientForMembershipOrg,
|
||||||
|
validateMembershipOrg
|
||||||
|
} from '../helpers/membershipOrg';
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: transform
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,32 +23,24 @@ type req = 'params' | 'body' | 'query';
|
|||||||
*/
|
*/
|
||||||
const requireMembershipOrgAuth = ({
|
const requireMembershipOrgAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
location = 'params'
|
acceptedStatuses,
|
||||||
|
locationMembershipOrgId = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
location?: req;
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
|
locationMembershipOrgId?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
const { membershipId } = req[locationMembershipOrgId];
|
||||||
const { membershipId } = req[location];
|
|
||||||
const membershipOrg = await MembershipOrg.findById(membershipId);
|
|
||||||
|
|
||||||
if (!membershipOrg) throw new Error('Failed to find target organization membership');
|
req.membershipOrg = await validateClientForMembershipOrg({
|
||||||
|
authData: req.authData,
|
||||||
|
membershipOrgId: new Types.ObjectId(membershipId),
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
const targetMembership = await validateMembership({
|
return next();
|
||||||
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'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +1,46 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { IOrganization, MembershipOrg } from '../models';
|
import { IOrganization, MembershipOrg } from '../models';
|
||||||
import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
|
import { UnauthorizedRequestError, ValidationError } from '../utils/errors';
|
||||||
|
import { validateMembershipOrg } from '../helpers/membershipOrg';
|
||||||
|
import { validateClientForOrganization } from '../helpers/organization';
|
||||||
|
|
||||||
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if user on request is a member with proper roles for organization
|
* Validate if user on request is a member with proper roles for organization
|
||||||
* on request params.
|
* on request params.
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String[]} obj.acceptedRoles - accepted organization roles
|
* @param {String[]} obj.acceptedRoles - accepted organization roles
|
||||||
* @param {String[]} obj.acceptedStatuses - accepted organization statuses
|
* @param {String[]} obj.accepteStatuses - accepted organization statuses
|
||||||
*/
|
*/
|
||||||
const requireOrganizationAuth = ({
|
const requireOrganizationAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
acceptedStatuses
|
acceptedStatuses,
|
||||||
|
locationOrganizationId = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
acceptedStatuses: string[];
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
|
locationOrganizationId?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
// organization authorization middleware
|
const { organizationId } = req[locationOrganizationId];
|
||||||
|
|
||||||
// validate organization membership
|
const { organization, membershipOrg } = await validateClientForOrganization({
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
authData: req.authData,
|
||||||
user: req.user._id,
|
organizationId: new Types.ObjectId(organizationId),
|
||||||
organization: req.params.organizationId
|
acceptedRoles,
|
||||||
}).populate<{ organization: IOrganization }>('organization');
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
if (organization) {
|
||||||
if (!membershipOrg) {
|
req.organization = organization;
|
||||||
return next(UnauthorizedRequestError({message: "You're not a member of this Organization."}))
|
|
||||||
}
|
|
||||||
//TODO is this important to validate? I mean is it possible to save wrong role to database or get wrong role from databse? - Zamion101
|
|
||||||
if (!acceptedRoles.includes(membershipOrg.role)) {
|
|
||||||
return next(ValidationError({message: 'Failed to validate Organization Membership Role'}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!acceptedStatuses.includes(membershipOrg.status)) {
|
if (membershipOrg) {
|
||||||
return next(ValidationError({message: 'Failed to validate Organization Membership Status'}))
|
req.membershipOrg = membershipOrg;
|
||||||
}
|
}
|
||||||
|
|
||||||
req.membershipOrg = membershipOrg;
|
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { UnauthorizedRequestError, SecretNotFoundError } from '../utils/errors';
|
import { UnauthorizedRequestError, SecretNotFoundError } from '../utils/errors';
|
||||||
import { Secret } from '../models';
|
import { Secret } from '../models';
|
||||||
import {
|
import {
|
||||||
validateMembership
|
validateMembership
|
||||||
} from '../helpers/membership';
|
} from '../helpers/membership';
|
||||||
|
import {
|
||||||
|
validateClientForSecret
|
||||||
|
} from '../helpers/secrets';
|
||||||
|
|
||||||
// note: used for old /v1/secret and /v2/secret routes.
|
// note: used for old /v1/secret and /v2/secret routes.
|
||||||
// newer /v2/secrets routes use [requireSecretsAuth] middleware
|
// newer /v2/secrets routes use [requireSecretsAuth] middleware with the exception
|
||||||
|
// of some /ee endpoints
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if user on request has proper membership to modify secret.
|
* Validate if user on request has proper membership to modify secret.
|
||||||
@@ -15,34 +20,25 @@ import {
|
|||||||
* @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing
|
* @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing
|
||||||
*/
|
*/
|
||||||
const requireSecretAuth = ({
|
const requireSecretAuth = ({
|
||||||
acceptedRoles
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
|
requiredPermissions: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
const { secretId } = req.params;
|
||||||
const { secretId } = req.params;
|
|
||||||
|
|
||||||
const secret = await Secret.findById(secretId);
|
const secret = await validateClientForSecret({
|
||||||
|
authData: req.authData,
|
||||||
|
secretId: new Types.ObjectId(secretId),
|
||||||
|
acceptedRoles,
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
if (!secret) {
|
req._secret = secret;
|
||||||
return next(SecretNotFoundError({
|
|
||||||
message: 'Failed to find secret'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
await validateMembership({
|
next();
|
||||||
userId: req.user._id.toString(),
|
|
||||||
workspaceId: secret.workspace.toString(),
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
|
|
||||||
req._secret = secret;
|
|
||||||
|
|
||||||
next();
|
|
||||||
} catch (err) {
|
|
||||||
return next(UnauthorizedRequestError({ message: 'Unable to authenticate secret' }));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +1,35 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
import { Secret, Membership } from '../models';
|
import { Secret, Membership } from '../models';
|
||||||
import { validateSecrets } from '../helpers/secret';
|
import { validateClientForSecrets } from '../helpers/secrets';
|
||||||
|
|
||||||
// TODO: make this work for delete route
|
|
||||||
|
|
||||||
const requireSecretsAuth = ({
|
const requireSecretsAuth = ({
|
||||||
acceptedRoles
|
acceptedRoles,
|
||||||
|
requiredPermissions = []
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: string[];
|
||||||
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
let secrets;
|
let secretIds = [];
|
||||||
try {
|
if (Array.isArray(req.body.secrets)) {
|
||||||
if (Array.isArray(req.body.secrets)) {
|
secretIds = req.body.secrets.map((s: any) => s.id);
|
||||||
// case: validate multiple secrets
|
} else if (typeof req.body.secrets === 'object') {
|
||||||
secrets = await validateSecrets({
|
secretIds = [req.body.secrets.id];
|
||||||
userId: req.user._id.toString(),
|
} else if (Array.isArray(req.body.secretIds)) {
|
||||||
secretIds: req.body.secrets.map((s: any) => s.id)
|
secretIds = req.body.secretIds;
|
||||||
});
|
} else if (typeof req.body.secretIds === 'string') {
|
||||||
} else if (typeof req.body.secrets === 'object') { // change this to check for object
|
secretIds = [req.body.secretIds];
|
||||||
// case: validate 1 secret
|
|
||||||
secrets = await validateSecrets({
|
|
||||||
userId: req.user._id.toString(),
|
|
||||||
secretIds: [req.body.secrets.id]
|
|
||||||
});
|
|
||||||
} else if (Array.isArray(req.body.secretIds)) {
|
|
||||||
secrets = await validateSecrets({
|
|
||||||
userId: req.user._id.toString(),
|
|
||||||
secretIds: req.body.secretIds
|
|
||||||
});
|
|
||||||
} else if (typeof req.body.secretIds === 'string') {
|
|
||||||
// case: validate secretIds
|
|
||||||
secrets = await validateSecrets({
|
|
||||||
userId: req.user._id.toString(),
|
|
||||||
secretIds: [req.body.secretIds]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
req.secrets = secrets;
|
|
||||||
return next();
|
|
||||||
} catch (err) {
|
|
||||||
return next(UnauthorizedRequestError({ message: 'Unable to authenticate secret(s)' }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.secrets = await validateClientForSecrets({
|
||||||
|
authData: req.authData,
|
||||||
|
secretIds: secretIds.map((secretId: string) => new Types.ObjectId(secretId)),
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
return next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
|
import { ServiceAccount } from '../models';
|
||||||
|
import {
|
||||||
|
ServiceAccountNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
validateMembershipOrg
|
||||||
|
} from '../helpers/membershipOrg';
|
||||||
|
import {
|
||||||
|
validateClientForServiceAccount
|
||||||
|
} from '../helpers/serviceAccount';
|
||||||
|
|
||||||
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
|
const requireServiceAccountAuth = ({
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses,
|
||||||
|
locationServiceAccountId = 'params',
|
||||||
|
requiredPermissions = []
|
||||||
|
}: {
|
||||||
|
acceptedRoles: string[];
|
||||||
|
acceptedStatuses: string[];
|
||||||
|
locationServiceAccountId?: req;
|
||||||
|
requiredPermissions?: string[];
|
||||||
|
}) => {
|
||||||
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const serviceAccountId = req[locationServiceAccountId].serviceAccountId;
|
||||||
|
|
||||||
|
req.serviceAccount = await validateClientForServiceAccount({
|
||||||
|
authData: req.authData,
|
||||||
|
serviceAccountId: new Types.ObjectId(serviceAccountId),
|
||||||
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default requireServiceAccountAuth;
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { ServiceAccount, ServiceAccountWorkspacePermission } from '../models';
|
||||||
|
import {
|
||||||
|
ServiceAccountNotFoundError
|
||||||
|
} from '../utils/errors';
|
||||||
|
import {
|
||||||
|
validateMembershipOrg
|
||||||
|
} from '../helpers/membershipOrg';
|
||||||
|
|
||||||
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
|
const requireServiceAccountWorkspacePermissionAuth = ({
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses,
|
||||||
|
location = 'params'
|
||||||
|
}: {
|
||||||
|
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
|
||||||
|
acceptedStatuses: Array<'invited' | 'accepted'>;
|
||||||
|
location?: req;
|
||||||
|
}) => {
|
||||||
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const serviceAccountWorkspacePermissionId = req[location].serviceAccountWorkspacePermissionId;
|
||||||
|
const serviceAccountWorkspacePermission = await ServiceAccountWorkspacePermission.findById(serviceAccountWorkspacePermissionId);
|
||||||
|
|
||||||
|
if (!serviceAccountWorkspacePermission) {
|
||||||
|
return next(ServiceAccountNotFoundError({ message: 'Failed to locate Service Account workspace permission' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceAccount = await ServiceAccount.findById(serviceAccountWorkspacePermission.serviceAccount);
|
||||||
|
|
||||||
|
if (!serviceAccount) {
|
||||||
|
return next(ServiceAccountNotFoundError({ message: 'Failed to locate Service Account' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serviceAccount.user.toString() !== req.user.id.toString()) {
|
||||||
|
// case: creator of the service account is different from
|
||||||
|
// the user on the request -> apply middleware role/status validation
|
||||||
|
await validateMembershipOrg({
|
||||||
|
userId: req.user._id,
|
||||||
|
organizationId: serviceAccount.organization,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
req.serviceAccount = serviceAccount;
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default requireServiceAccountWorkspacePermissionAuth;
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { ServiceToken, ServiceTokenData } from '../models';
|
import { ServiceToken, ServiceTokenData } from '../models';
|
||||||
|
import { validateClientForServiceTokenData } from '../helpers/serviceTokenData';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
import { AccountNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
import { AccountNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
@@ -9,30 +11,17 @@ const requireServiceTokenDataAuth = ({
|
|||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
location = 'params'
|
location = 'params'
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
location?: req;
|
location?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const { serviceTokenDataId } = req[location];
|
const { serviceTokenDataId } = req[location];
|
||||||
|
|
||||||
const serviceTokenData = await ServiceTokenData
|
req.serviceTokenData = await validateClientForServiceTokenData({
|
||||||
.findById(req[location].serviceTokenDataId)
|
authData: req.authData,
|
||||||
.select('+encryptedKey +iv +tag').populate('user');
|
serviceTokenDataId: new Types.ObjectId(serviceTokenDataId),
|
||||||
|
acceptedRoles
|
||||||
if (!serviceTokenData) {
|
});
|
||||||
return next(AccountNotFoundError({ message: 'Failed to locate service token data' }));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.user) {
|
|
||||||
// case: jwt auth
|
|
||||||
await validateMembership({
|
|
||||||
userId: req.user._id.toString(),
|
|
||||||
workspaceId: serviceTokenData.workspace.toString(),
|
|
||||||
acceptedRoles
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
req.serviceTokenData = serviceTokenData;
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
|
import { validateClientForWorkspace } from '../helpers/workspace';
|
||||||
import { UnauthorizedRequestError } from '../utils/errors';
|
import { UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
@@ -13,38 +15,33 @@ type req = 'params' | 'body' | 'query';
|
|||||||
*/
|
*/
|
||||||
const requireWorkspaceAuth = ({
|
const requireWorkspaceAuth = ({
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
location = 'params'
|
locationWorkspaceId,
|
||||||
|
locationEnvironment = undefined,
|
||||||
|
requiredPermissions = []
|
||||||
}: {
|
}: {
|
||||||
acceptedRoles: string[];
|
acceptedRoles: Array<'admin' | 'member'>;
|
||||||
location?: req;
|
locationWorkspaceId: req;
|
||||||
|
locationEnvironment?: req | undefined;
|
||||||
|
requiredPermissions?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
const workspaceId = req[locationWorkspaceId]?.workspaceId;
|
||||||
const { workspaceId } = req[location];
|
const environment = locationEnvironment ? req[locationEnvironment]?.environment : undefined;
|
||||||
|
|
||||||
if (req.user) {
|
// validate clients
|
||||||
// case: jwt auth
|
const { membership } = await validateClientForWorkspace({
|
||||||
const membership = await validateMembership({
|
authData: req.authData,
|
||||||
userId: req.user._id.toString(),
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
workspaceId,
|
environment,
|
||||||
acceptedRoles
|
acceptedRoles,
|
||||||
});
|
requiredPermissions
|
||||||
|
});
|
||||||
|
|
||||||
req.membership = membership;
|
if (membership) {
|
||||||
}
|
req.membership = membership;
|
||||||
|
|
||||||
if (
|
|
||||||
req.serviceTokenData
|
|
||||||
&& req.serviceTokenData.workspace.toString() !== workspaceId
|
|
||||||
&& req.serviceTokenData.environment !== req.body.environment
|
|
||||||
) {
|
|
||||||
next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return next();
|
|
||||||
} catch (err) {
|
|
||||||
return next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Schema, model, Types } from 'mongoose';
|
|||||||
export interface IAPIKeyData {
|
export interface IAPIKeyData {
|
||||||
name: string;
|
name: string;
|
||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
|
lastUsed: Date;
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
secretHash: string;
|
secretHash: string;
|
||||||
}
|
}
|
||||||
@@ -18,6 +19,9 @@ const apiKeyDataSchema = new Schema<IAPIKeyData>(
|
|||||||
ref: 'User',
|
ref: 'User',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
lastUsed: {
|
||||||
|
type: Date
|
||||||
|
},
|
||||||
expiresAt: {
|
expiresAt: {
|
||||||
type: Date
|
type: Date
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import MembershipOrg, { IMembershipOrg } from './membershipOrg';
|
|||||||
import Organization, { IOrganization } from './organization';
|
import Organization, { IOrganization } from './organization';
|
||||||
import Secret, { ISecret } from './secret';
|
import Secret, { ISecret } from './secret';
|
||||||
import ServiceToken, { IServiceToken } from './serviceToken';
|
import ServiceToken, { IServiceToken } from './serviceToken';
|
||||||
|
import ServiceAccount, { IServiceAccount } from './serviceAccount'; // new
|
||||||
|
import ServiceAccountKey, { IServiceAccountKey } from './serviceAccountKey'; // new
|
||||||
|
import ServiceAccountOrganizationPermission, { IServiceAccountOrganizationPermission } from './serviceAccountOrganizationPermission'; // new
|
||||||
|
import ServiceAccountWorkspacePermission, { IServiceAccountWorkspacePermission } from './serviceAccountWorkspacePermission'; // new
|
||||||
import TokenData, { ITokenData } from './tokenData';
|
import TokenData, { ITokenData } from './tokenData';
|
||||||
import User, { IUser } from './user';
|
import User, { IUser } from './user';
|
||||||
import UserAction, { IUserAction } from './userAction';
|
import UserAction, { IUserAction } from './userAction';
|
||||||
@@ -43,6 +47,14 @@ export {
|
|||||||
ISecret,
|
ISecret,
|
||||||
ServiceToken,
|
ServiceToken,
|
||||||
IServiceToken,
|
IServiceToken,
|
||||||
|
ServiceAccount,
|
||||||
|
IServiceAccount,
|
||||||
|
ServiceAccountKey,
|
||||||
|
IServiceAccountKey,
|
||||||
|
ServiceAccountOrganizationPermission,
|
||||||
|
IServiceAccountOrganizationPermission,
|
||||||
|
ServiceAccountWorkspacePermission,
|
||||||
|
IServiceAccountWorkspacePermission,
|
||||||
TokenData,
|
TokenData,
|
||||||
ITokenData,
|
ITokenData,
|
||||||
User,
|
User,
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import {
|
|||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
INTEGRATION_RENDER,
|
INTEGRATION_RENDER,
|
||||||
|
INTEGRATION_RAILWAY,
|
||||||
INTEGRATION_FLYIO,
|
INTEGRATION_FLYIO,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
INTEGRATION_TRAVISCI,
|
INTEGRATION_TRAVISCI,
|
||||||
|
INTEGRATION_SUPABASE
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
|
|
||||||
export interface IIntegration {
|
export interface IIntegration {
|
||||||
@@ -20,9 +22,12 @@ export interface IIntegration {
|
|||||||
environment: string;
|
environment: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
app: string;
|
app: string;
|
||||||
|
appId: string;
|
||||||
owner: string;
|
owner: string;
|
||||||
targetEnvironment: string;
|
targetEnvironment: string;
|
||||||
appId: string;
|
targetEnvironmentId: string;
|
||||||
|
targetService: string;
|
||||||
|
targetServiceId: string;
|
||||||
path: string;
|
path: string;
|
||||||
region: string;
|
region: string;
|
||||||
integration:
|
integration:
|
||||||
@@ -35,9 +40,11 @@ export interface IIntegration {
|
|||||||
| 'github'
|
| 'github'
|
||||||
| 'gitlab'
|
| 'gitlab'
|
||||||
| 'render'
|
| 'render'
|
||||||
|
| 'railway'
|
||||||
| 'flyio'
|
| 'flyio'
|
||||||
| 'circleci'
|
| 'circleci'
|
||||||
| 'travisci';
|
| 'travisci'
|
||||||
|
| 'supabase';
|
||||||
integrationAuth: Types.ObjectId;
|
integrationAuth: Types.ObjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +78,20 @@ const integrationSchema = new Schema<IIntegration>(
|
|||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
targetEnvironmentId: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
targetService: {
|
||||||
|
// railway-specific service
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
targetServiceId: {
|
||||||
|
// railway-specific service
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
owner: {
|
owner: {
|
||||||
// github-specific repo owner-login
|
// github-specific repo owner-login
|
||||||
type: String,
|
type: String,
|
||||||
@@ -78,6 +99,7 @@ const integrationSchema = new Schema<IIntegration>(
|
|||||||
},
|
},
|
||||||
path: {
|
path: {
|
||||||
// aws-parameter-store-specific path
|
// aws-parameter-store-specific path
|
||||||
|
// (also) vercel preview-branch
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
@@ -98,9 +120,11 @@ const integrationSchema = new Schema<IIntegration>(
|
|||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
INTEGRATION_RENDER,
|
INTEGRATION_RENDER,
|
||||||
|
INTEGRATION_RAILWAY,
|
||||||
INTEGRATION_FLYIO,
|
INTEGRATION_FLYIO,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
INTEGRATION_TRAVISCI,
|
INTEGRATION_TRAVISCI,
|
||||||
|
INTEGRATION_SUPABASE
|
||||||
],
|
],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Schema, model, Types } from "mongoose";
|
import { Schema, model, Types, Document } from "mongoose";
|
||||||
import {
|
import {
|
||||||
INTEGRATION_AZURE_KEY_VAULT,
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
INTEGRATION_AWS_PARAMETER_STORE,
|
INTEGRATION_AWS_PARAMETER_STORE,
|
||||||
@@ -9,15 +9,17 @@ import {
|
|||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
INTEGRATION_RENDER,
|
INTEGRATION_RENDER,
|
||||||
|
INTEGRATION_RAILWAY,
|
||||||
INTEGRATION_FLYIO,
|
INTEGRATION_FLYIO,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
INTEGRATION_TRAVISCI,
|
INTEGRATION_TRAVISCI,
|
||||||
|
INTEGRATION_SUPABASE,
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
|
|
||||||
export interface IIntegrationAuth {
|
export interface IIntegrationAuth extends Document {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'aws-parameter-store' | 'aws-secret-manager';
|
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager';
|
||||||
teamId: string;
|
teamId: string;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
refreshCiphertext?: string;
|
refreshCiphertext?: string;
|
||||||
@@ -51,9 +53,11 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
|||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_GITLAB,
|
INTEGRATION_GITLAB,
|
||||||
INTEGRATION_RENDER,
|
INTEGRATION_RENDER,
|
||||||
|
INTEGRATION_RAILWAY,
|
||||||
INTEGRATION_FLYIO,
|
INTEGRATION_FLYIO,
|
||||||
INTEGRATION_CIRCLECI,
|
INTEGRATION_CIRCLECI,
|
||||||
INTEGRATION_TRAVISCI,
|
INTEGRATION_TRAVISCI,
|
||||||
|
INTEGRATION_SUPABASE
|
||||||
],
|
],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Schema, model, Types } from 'mongoose';
|
import { Schema, model, Types, Document } from 'mongoose';
|
||||||
import { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED } from '../variables';
|
import { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED } from '../variables';
|
||||||
|
|
||||||
export interface IMembershipOrg {
|
export interface IMembershipOrg extends Document {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
inviteEmail: string;
|
inviteEmail: string;
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { Schema, model, Types, Document } from 'mongoose';
|
||||||
|
|
||||||
|
export interface IServiceAccount extends Document {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
name: string;
|
||||||
|
organization: Types.ObjectId;
|
||||||
|
user: Types.ObjectId;
|
||||||
|
publicKey: string;
|
||||||
|
lastUsed: Date;
|
||||||
|
expiresAt: Date;
|
||||||
|
secretHash: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceAccountSchema = new Schema<IServiceAccount>(
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'Organization',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
user: { // user who created the service account
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'User',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
publicKey: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
lastUsed: {
|
||||||
|
type: Date
|
||||||
|
},
|
||||||
|
expiresAt: {
|
||||||
|
type: Date
|
||||||
|
},
|
||||||
|
secretHash: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
select: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const ServiceAccount = model<IServiceAccount>('ServiceAccount', serviceAccountSchema);
|
||||||
|
|
||||||
|
export default ServiceAccount;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Schema, model, Types } from 'mongoose';
|
||||||
|
|
||||||
|
export interface IServiceAccountKey {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
encryptedKey: string;
|
||||||
|
nonce: string;
|
||||||
|
sender: Types.ObjectId;
|
||||||
|
serviceAccount: Types.ObjectId;
|
||||||
|
workspace: Types.ObjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceAccountKeySchema = new Schema<IServiceAccountKey>(
|
||||||
|
{
|
||||||
|
encryptedKey: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
nonce: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
sender: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
serviceAccount: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceAccount',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
workspace: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'Workspace',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const ServiceAccountKey = model<IServiceAccountKey>('ServiceAccountKey', serviceAccountKeySchema);
|
||||||
|
|
||||||
|
export default ServiceAccountKey;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Schema, model, Types, Document } from 'mongoose';
|
||||||
|
|
||||||
|
export interface IServiceAccountOrganizationPermission extends Document {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
serviceAccount: Types.ObjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceAccountOrganizationPermissionSchema = new Schema<IServiceAccountOrganizationPermission>(
|
||||||
|
{
|
||||||
|
serviceAccount: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceAccount',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const ServiceAccountOrganizationPermission = model<IServiceAccountOrganizationPermission>('ServiceAccountOrganizationPermission', serviceAccountOrganizationPermissionSchema);
|
||||||
|
|
||||||
|
export default ServiceAccountOrganizationPermission;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Schema, model, Types, Document } from 'mongoose';
|
||||||
|
|
||||||
|
export interface IServiceAccountWorkspacePermission extends Document {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
serviceAccount: Types.ObjectId;
|
||||||
|
workspace: Types.ObjectId;
|
||||||
|
environment: string;
|
||||||
|
read: boolean;
|
||||||
|
write: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceAccountWorkspacePermissionSchema = new Schema<IServiceAccountWorkspacePermission>(
|
||||||
|
{
|
||||||
|
serviceAccount: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceAccount',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
workspace:{
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'Workspace',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
read: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
write: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const ServiceAccountWorkspacePermission = model<IServiceAccountWorkspacePermission>('ServiceAccountWorkspacePermission', serviceAccountWorkspacePermissionSchema);
|
||||||
|
|
||||||
|
export default ServiceAccountWorkspacePermission;
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
import { Schema, model, Types } from 'mongoose';
|
import { Schema, model, Types, Document } from 'mongoose';
|
||||||
|
|
||||||
export interface IServiceTokenData {
|
export interface IServiceTokenData extends Document {
|
||||||
|
_id: Types.ObjectId;
|
||||||
name: string;
|
name: string;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
environment: string;
|
environment: string;
|
||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
|
serviceAccount: Types.ObjectId;
|
||||||
|
lastUsed: Date;
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
secretHash: string;
|
secretHash: string;
|
||||||
encryptedKey: string;
|
encryptedKey: string;
|
||||||
@@ -24,14 +27,20 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
|
|||||||
ref: 'Workspace',
|
ref: 'Workspace',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
environment: { // TODO: adapt to upcoming environment id
|
environment: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'User',
|
ref: 'User'
|
||||||
required: true
|
},
|
||||||
|
serviceAccount: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'ServiceAccount'
|
||||||
|
},
|
||||||
|
lastUsed: {
|
||||||
|
type: Date
|
||||||
},
|
},
|
||||||
expiresAt: {
|
expiresAt: {
|
||||||
type: Date
|
type: Date
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { body } from 'express-validator';
|
|||||||
import { requireAuth, validateRequest } from '../../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { authController } from '../../controllers/v1';
|
import { authController } from '../../controllers/v1';
|
||||||
import { authLimiter } from '../../helpers/rateLimiter';
|
import { authLimiter } from '../../helpers/rateLimiter';
|
||||||
|
import { AUTH_MODE_JWT } from '../../variables';
|
||||||
|
|
||||||
router.post('/token', validateRequest, authController.getNewToken);
|
router.post('/token', validateRequest, authController.getNewToken);
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ router.post(
|
|||||||
'/logout',
|
'/logout',
|
||||||
authLimiter,
|
authLimiter,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
authController.logout
|
authController.logout
|
||||||
);
|
);
|
||||||
@@ -37,7 +38,7 @@ router.post(
|
|||||||
router.post(
|
router.post(
|
||||||
'/checkAuth',
|
'/checkAuth',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
authController.checkAuth
|
authController.checkAuth
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,15 +8,16 @@ import {
|
|||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { botController } from '../../controllers/v1';
|
import { botController } from '../../controllers/v1';
|
||||||
import { ADMIN, MEMBER } from '../../variables';
|
import { ADMIN, MEMBER, AUTH_MODE_JWT } from '../../variables';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim().notEmpty(),
|
param('workspaceId').exists().trim().notEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -26,7 +27,7 @@ router.get(
|
|||||||
router.patch(
|
router.patch(
|
||||||
'/:botId/active',
|
'/:botId/active',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireBotAuth({
|
requireBotAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
|||||||
@@ -6,14 +6,19 @@ import {
|
|||||||
requireIntegrationAuthorizationAuth,
|
requireIntegrationAuthorizationAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { ADMIN, MEMBER } from '../../variables';
|
import {
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../../variables';
|
||||||
import { body, param } from 'express-validator';
|
import { body, param } from 'express-validator';
|
||||||
import { integrationController } from '../../controllers/v1';
|
import { integrationController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post( // new: add new integration for integration auth
|
router.post( // new: add new integration for integration auth
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]
|
||||||
}),
|
}),
|
||||||
requireIntegrationAuthorizationAuth({
|
requireIntegrationAuthorizationAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
@@ -25,6 +30,9 @@ router.post( // new: add new integration for integration auth
|
|||||||
body('appId').trim(),
|
body('appId').trim(),
|
||||||
body('sourceEnvironment').trim(),
|
body('sourceEnvironment').trim(),
|
||||||
body('targetEnvironment').trim(),
|
body('targetEnvironment').trim(),
|
||||||
|
body('targetEnvironmentId').trim(),
|
||||||
|
body('targetService').trim(),
|
||||||
|
body('targetServiceId').trim(),
|
||||||
body('owner').trim(),
|
body('owner').trim(),
|
||||||
body('path').trim(),
|
body('path').trim(),
|
||||||
body('region').trim(),
|
body('region').trim(),
|
||||||
@@ -35,7 +43,7 @@ router.post( // new: add new integration for integration auth
|
|||||||
router.patch(
|
router.patch(
|
||||||
'/:integrationId',
|
'/:integrationId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireIntegrationAuth({
|
requireIntegrationAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
@@ -54,7 +62,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
'/:integrationId',
|
'/:integrationId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireIntegrationAuth({
|
requireIntegrationAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
|||||||
@@ -7,13 +7,18 @@ import {
|
|||||||
requireIntegrationAuthorizationAuth,
|
requireIntegrationAuthorizationAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { ADMIN, MEMBER } from '../../variables';
|
import {
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../../variables';
|
||||||
import { integrationAuthController } from '../../controllers/v1';
|
import { integrationAuthController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/integration-options',
|
'/integration-options',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
integrationAuthController.getIntegrationOptions
|
integrationAuthController.getIntegrationOptions
|
||||||
);
|
);
|
||||||
@@ -21,7 +26,7 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:integrationAuthId',
|
'/:integrationAuthId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireIntegrationAuthorizationAuth({
|
requireIntegrationAuthorizationAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
@@ -34,11 +39,11 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
'/oauth-token',
|
'/oauth-token',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
location: 'body'
|
locationWorkspaceId: 'body'
|
||||||
}),
|
}),
|
||||||
body('workspaceId').exists().trim().notEmpty(),
|
body('workspaceId').exists().trim().notEmpty(),
|
||||||
body('code').exists().trim().notEmpty(),
|
body('code').exists().trim().notEmpty(),
|
||||||
@@ -49,25 +54,25 @@ router.post(
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/access-token',
|
'/access-token',
|
||||||
requireAuth({
|
|
||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
|
||||||
}),
|
|
||||||
requireWorkspaceAuth({
|
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
|
||||||
location: 'body'
|
|
||||||
}),
|
|
||||||
body('workspaceId').exists().trim().notEmpty(),
|
body('workspaceId').exists().trim().notEmpty(),
|
||||||
body('accessId').trim(),
|
body('accessId').trim(),
|
||||||
body('accessToken').exists().trim().notEmpty(),
|
body('accessToken').exists().trim().notEmpty(),
|
||||||
body('integration').exists().trim().notEmpty(),
|
body('integration').exists().trim().notEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'body'
|
||||||
|
}),
|
||||||
integrationAuthController.saveIntegrationAccessToken
|
integrationAuthController.saveIntegrationAccessToken
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:integrationAuthId/apps',
|
'/:integrationAuthId/apps',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireIntegrationAuthorizationAuth({
|
requireIntegrationAuthorizationAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
@@ -81,7 +86,7 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:integrationAuthId/teams',
|
'/:integrationAuthId/teams',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireIntegrationAuthorizationAuth({
|
requireIntegrationAuthorizationAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
@@ -91,10 +96,53 @@ router.get(
|
|||||||
integrationAuthController.getIntegrationAuthTeams
|
integrationAuthController.getIntegrationAuthTeams
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:integrationAuthId/vercel/branches',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
requireIntegrationAuthorizationAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
}),
|
||||||
|
param('integrationAuthId').exists().isString(),
|
||||||
|
query('appId').exists().isString(),
|
||||||
|
query('teamId').optional().isString(),
|
||||||
|
validateRequest,
|
||||||
|
integrationAuthController.getIntegrationAuthVercelBranches
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:integrationAuthId/railway/environments',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
requireIntegrationAuthorizationAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
}),
|
||||||
|
param('integrationAuthId').exists().isString(),
|
||||||
|
query('appId').exists().isString(),
|
||||||
|
validateRequest,
|
||||||
|
integrationAuthController.getIntegrationAuthRailwayEnvironments
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:integrationAuthId/railway/services',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
requireIntegrationAuthorizationAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
}),
|
||||||
|
param('integrationAuthId').exists().isString(),
|
||||||
|
query('appId').exists().isString(),
|
||||||
|
validateRequest,
|
||||||
|
integrationAuthController.getIntegrationAuthRailwayServices
|
||||||
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:integrationAuthId',
|
'/:integrationAuthId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireIntegrationAuthorizationAuth({
|
requireIntegrationAuthorizationAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ const router = express.Router();
|
|||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import { requireAuth, validateRequest } from '../../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { membershipOrgController } from '../../controllers/v1';
|
import { membershipOrgController } from '../../controllers/v1';
|
||||||
|
import { AUTH_MODE_JWT } from '../../variables';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/signup',
|
'/signup',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
body('inviteeEmail').exists().trim().notEmpty().isEmail(),
|
body('inviteeEmail').exists().trim().notEmpty().isEmail(),
|
||||||
body('organizationId').exists().trim().notEmpty(),
|
body('organizationId').exists().trim().notEmpty(),
|
||||||
|
|||||||
@@ -6,16 +6,17 @@ import {
|
|||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { body, param } from 'express-validator';
|
import { body, param } from 'express-validator';
|
||||||
import { ADMIN, MEMBER } from '../../variables';
|
import { ADMIN, MEMBER, AUTH_MODE_JWT } from '../../variables';
|
||||||
import { keyController } from '../../controllers/v1';
|
import { keyController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
body('key').exists(),
|
body('key').exists(),
|
||||||
@@ -26,10 +27,11 @@ router.post(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/latest',
|
'/:workspaceId/latest',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId'),
|
param('workspaceId'),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import { body, param } from 'express-validator';
|
|||||||
import { requireAuth, validateRequest } from '../../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { membershipController } from '../../controllers/v1';
|
import { membershipController } from '../../controllers/v1';
|
||||||
import { membershipController as EEMembershipControllers } from '../../ee/controllers/v1';
|
import { membershipController as EEMembershipControllers } from '../../ee/controllers/v1';
|
||||||
|
import { AUTH_MODE_JWT } from '../../variables';
|
||||||
|
|
||||||
// note: ALL DEPRECIATED (moved to api/v2/workspace/:workspaceId/memberships/:membershipId)
|
// note: ALL DEPRECIATED (moved to api/v2/workspace/:workspaceId/memberships/:membershipId)
|
||||||
|
|
||||||
router.get( // used for old CLI (deprecate)
|
router.get( // used for old CLI (deprecate)
|
||||||
'/:workspaceId/connect',
|
'/:workspaceId/connect',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -20,7 +21,7 @@ router.get( // used for old CLI (deprecate)
|
|||||||
router.delete(
|
router.delete(
|
||||||
'/:membershipId',
|
'/:membershipId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
param('membershipId').exists().trim(),
|
param('membershipId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -30,7 +31,7 @@ router.delete(
|
|||||||
router.post(
|
router.post(
|
||||||
'/:membershipId/change-role',
|
'/:membershipId/change-role',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
body('role').exists().trim(),
|
body('role').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -40,7 +41,7 @@ router.post(
|
|||||||
router.post(
|
router.post(
|
||||||
'/:membershipId/deny-permissions',
|
'/:membershipId/deny-permissions',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
param('membershipId').isMongoId().exists().trim(),
|
param('membershipId').isMongoId().exists().trim(),
|
||||||
body('permissions').isArray().exists(),
|
body('permissions').isArray().exists(),
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ const router = express.Router();
|
|||||||
import { param } from 'express-validator';
|
import { param } from 'express-validator';
|
||||||
import { requireAuth, validateRequest } from '../../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { membershipOrgController } from '../../controllers/v1';
|
import { membershipOrgController } from '../../controllers/v1';
|
||||||
|
import { AUTH_MODE_JWT } from '../../variables';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
// TODO
|
// TODO
|
||||||
'/membershipOrg/:membershipOrgId/change-role',
|
'/membershipOrg/:membershipOrgId/change-role',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
param('membershipOrgId'),
|
param('membershipOrgId'),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -18,7 +19,7 @@ router.post(
|
|||||||
router.delete(
|
router.delete(
|
||||||
'/:membershipOrgId',
|
'/:membershipOrgId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
param('membershipOrgId').exists().trim(),
|
param('membershipOrgId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
|
|||||||
@@ -6,13 +6,19 @@ import {
|
|||||||
requireOrganizationAuth,
|
requireOrganizationAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { OWNER, ADMIN, MEMBER, ACCEPTED } from '../../variables';
|
import {
|
||||||
|
OWNER,
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
ACCEPTED,
|
||||||
|
AUTH_MODE_JWT
|
||||||
|
} from '../../variables';
|
||||||
import { organizationController } from '../../controllers/v1';
|
import { organizationController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get( // deprecated (moved to api/v2/users/me/organizations)
|
router.get( // deprecated (moved to api/v2/users/me/organizations)
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
organizationController.getOrganizations
|
organizationController.getOrganizations
|
||||||
);
|
);
|
||||||
@@ -20,7 +26,7 @@ router.get( // deprecated (moved to api/v2/users/me/organizations)
|
|||||||
router.post( // not used on frontend
|
router.post( // not used on frontend
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
body('organizationName').exists().trim().notEmpty(),
|
body('organizationName').exists().trim().notEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -30,7 +36,7 @@ router.post( // not used on frontend
|
|||||||
router.get(
|
router.get(
|
||||||
'/:organizationId',
|
'/:organizationId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
@@ -44,7 +50,7 @@ router.get(
|
|||||||
router.get( // deprecated (moved to api/v2/organizations/:organizationId/memberships)
|
router.get( // deprecated (moved to api/v2/organizations/:organizationId/memberships)
|
||||||
'/:organizationId/users',
|
'/:organizationId/users',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
@@ -58,7 +64,7 @@ router.get( // deprecated (moved to api/v2/organizations/:organizationId/members
|
|||||||
router.get(
|
router.get(
|
||||||
'/:organizationId/my-workspaces', // deprecated (moved to api/v2/organizations/:organizationId/workspaces)
|
'/:organizationId/my-workspaces', // deprecated (moved to api/v2/organizations/:organizationId/workspaces)
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
@@ -72,7 +78,7 @@ router.get(
|
|||||||
router.patch(
|
router.patch(
|
||||||
'/:organizationId/name',
|
'/:organizationId/name',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
@@ -87,7 +93,7 @@ router.patch(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:organizationId/incidentContactOrg',
|
'/:organizationId/incidentContactOrg',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
@@ -101,7 +107,7 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
'/:organizationId/incidentContactOrg',
|
'/:organizationId/incidentContactOrg',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
@@ -116,7 +122,7 @@ router.post(
|
|||||||
router.delete(
|
router.delete(
|
||||||
'/:organizationId/incidentContactOrg',
|
'/:organizationId/incidentContactOrg',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
@@ -131,7 +137,7 @@ router.delete(
|
|||||||
router.post(
|
router.post(
|
||||||
'/:organizationId/customer-portal-session',
|
'/:organizationId/customer-portal-session',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
@@ -145,7 +151,7 @@ router.post(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:organizationId/subscriptions',
|
'/:organizationId/subscriptions',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
@@ -159,7 +165,7 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:organizationId/workspace-memberships',
|
'/:organizationId/workspace-memberships',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import { body } from 'express-validator';
|
|||||||
import { requireAuth, requireSignupAuth, validateRequest } from '../../middleware';
|
import { requireAuth, requireSignupAuth, validateRequest } from '../../middleware';
|
||||||
import { passwordController } from '../../controllers/v1';
|
import { passwordController } from '../../controllers/v1';
|
||||||
import { passwordLimiter } from '../../helpers/rateLimiter';
|
import { passwordLimiter } from '../../helpers/rateLimiter';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT
|
||||||
|
} from '../../variables';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/srp1',
|
'/srp1',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
body('clientPublicKey').exists().isString().trim().notEmpty(),
|
body('clientPublicKey').exists().isString().trim().notEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -19,7 +22,7 @@ router.post(
|
|||||||
'/change-password',
|
'/change-password',
|
||||||
passwordLimiter,
|
passwordLimiter,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
body('clientProof').exists().trim().notEmpty(),
|
body('clientProof').exists().trim().notEmpty(),
|
||||||
body('protectedKey').exists().isString().trim().notEmpty(),
|
body('protectedKey').exists().isString().trim().notEmpty(),
|
||||||
@@ -62,7 +65,7 @@ router.post(
|
|||||||
'/backup-private-key',
|
'/backup-private-key',
|
||||||
passwordLimiter,
|
passwordLimiter,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
body('clientProof').exists().isString().trim().notEmpty(),
|
body('clientProof').exists().isString().trim().notEmpty(),
|
||||||
body('encryptedPrivateKey').exists().isString().trim().notEmpty(), // (backup) private key encrypted under a strong key
|
body('encryptedPrivateKey').exists().isString().trim().notEmpty(), // (backup) private key encrypted under a strong key
|
||||||
|
|||||||
@@ -8,15 +8,22 @@ import {
|
|||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { body, query, param } from 'express-validator';
|
import { body, query, param } from 'express-validator';
|
||||||
import { secretController } from '../../controllers/v1';
|
import { secretController } from '../../controllers/v1';
|
||||||
import { ADMIN, MEMBER } from '../../variables';
|
import {
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
AUTH_MODE_JWT
|
||||||
|
} from '../../variables';
|
||||||
|
|
||||||
|
// note to devs: these endpoints will be deprecated in favor of v2
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
body('secrets').exists(),
|
body('secrets').exists(),
|
||||||
body('keys').exists(),
|
body('keys').exists(),
|
||||||
@@ -30,10 +37,11 @@ router.post(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
query('environment').exists().trim(),
|
query('environment').exists().trim(),
|
||||||
query('channel'),
|
query('channel'),
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ import {
|
|||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import { ADMIN, MEMBER } from '../../variables';
|
import {
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
AUTH_MODE_JWT
|
||||||
|
} from '../../variables';
|
||||||
import { serviceTokenController } from '../../controllers/v1';
|
import { serviceTokenController } from '../../controllers/v1';
|
||||||
|
|
||||||
// note: deprecate service-token routes in favor of service-token data routes/structure
|
// note: deprecate service-token routes in favor of service-token data routes/structure
|
||||||
@@ -21,11 +25,11 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
location: 'body'
|
locationWorkspaceId: 'body'
|
||||||
}),
|
}),
|
||||||
body('name').exists().trim().notEmpty(),
|
body('name').exists().trim().notEmpty(),
|
||||||
body('workspaceId').exists().trim().notEmpty(),
|
body('workspaceId').exists().trim().notEmpty(),
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ import express from 'express';
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { requireAuth } from '../../middleware';
|
import { requireAuth } from '../../middleware';
|
||||||
import { userController } from '../../controllers/v1';
|
import { userController } from '../../controllers/v1';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT
|
||||||
|
} from '../../variables';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
userController.getUser
|
userController.getUser
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ const router = express.Router();
|
|||||||
import { requireAuth, validateRequest } from '../../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { body, query } from 'express-validator';
|
import { body, query } from 'express-validator';
|
||||||
import { userActionController } from '../../controllers/v1';
|
import { userActionController } from '../../controllers/v1';
|
||||||
|
import { AUTH_MODE_JWT } from '../../variables';
|
||||||
|
|
||||||
// note: [userAction] will be deprecated in /v2 in favor of [action]
|
// note: [userAction] will be deprecated in /v2 in favor of [action]
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
body('action'),
|
body('action'),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -18,7 +19,7 @@ router.post(
|
|||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
query('action'),
|
query('action'),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
|
|||||||
@@ -6,16 +6,21 @@ import {
|
|||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { ADMIN, MEMBER } from '../../variables';
|
import {
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
AUTH_MODE_JWT
|
||||||
|
} from '../../variables';
|
||||||
import { workspaceController, membershipController } from '../../controllers/v1';
|
import { workspaceController, membershipController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/keys',
|
'/:workspaceId/keys',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -25,10 +30,11 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/users',
|
'/:workspaceId/users',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -38,7 +44,7 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
workspaceController.getWorkspaces
|
workspaceController.getWorkspaces
|
||||||
);
|
);
|
||||||
@@ -46,10 +52,11 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -59,7 +66,7 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
body('workspaceName').exists().trim().notEmpty(),
|
body('workspaceName').exists().trim().notEmpty(),
|
||||||
body('organizationId').exists().trim().notEmpty(),
|
body('organizationId').exists().trim().notEmpty(),
|
||||||
@@ -70,10 +77,11 @@ router.post(
|
|||||||
router.delete(
|
router.delete(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN]
|
acceptedRoles: [ADMIN],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -83,10 +91,11 @@ router.delete(
|
|||||||
router.post(
|
router.post(
|
||||||
'/:workspaceId/name',
|
'/:workspaceId/name',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
body('name').exists().trim().notEmpty(),
|
body('name').exists().trim().notEmpty(),
|
||||||
@@ -97,10 +106,11 @@ router.post(
|
|||||||
router.post(
|
router.post(
|
||||||
'/:workspaceId/invite-signup',
|
'/:workspaceId/invite-signup',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
body('email').exists().trim().notEmpty(),
|
body('email').exists().trim().notEmpty(),
|
||||||
@@ -111,10 +121,11 @@ router.post(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/integrations',
|
'/:workspaceId/integrations',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -124,10 +135,11 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/authorizations',
|
'/:workspaceId/authorizations',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -137,10 +149,11 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/service-tokens', // deprecate
|
'/:workspaceId/service-tokens', // deprecate
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
import { param, body } from 'express-validator';
|
||||||
import {
|
import {
|
||||||
requireAuth,
|
requireAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { param, body } from 'express-validator';
|
|
||||||
import { apiKeyDataController } from '../../controllers/v2';
|
import { apiKeyDataController } from '../../controllers/v2';
|
||||||
|
import {
|
||||||
|
AUTH_MODE_JWT
|
||||||
|
} from '../../variables';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
apiKeyDataController.getAPIKeyData
|
apiKeyDataController.getAPIKeyData
|
||||||
);
|
);
|
||||||
@@ -18,7 +21,7 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
body('name').exists().trim(),
|
body('name').exists().trim(),
|
||||||
body('expiresIn'), // measured in ms
|
body('expiresIn'), // measured in ms
|
||||||
@@ -29,7 +32,7 @@ router.post(
|
|||||||
router.delete(
|
router.delete(
|
||||||
'/:apiKeyDataId',
|
'/:apiKeyDataId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
param('apiKeyDataId').exists().trim(),
|
param('apiKeyDataId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
|
|||||||
@@ -7,15 +7,20 @@ import {
|
|||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest,
|
validateRequest,
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { ADMIN, MEMBER } from '../../variables';
|
import {
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
AUTH_MODE_JWT
|
||||||
|
} from '../../variables';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:workspaceId/environments',
|
'/:workspaceId/environments',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt'],
|
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
body('environmentSlug').exists().trim(),
|
body('environmentSlug').exists().trim(),
|
||||||
@@ -27,10 +32,11 @@ router.post(
|
|||||||
router.put(
|
router.put(
|
||||||
'/:workspaceId/environments',
|
'/:workspaceId/environments',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt'],
|
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
body('environmentSlug').exists().trim(),
|
body('environmentSlug').exists().trim(),
|
||||||
@@ -43,10 +49,11 @@ router.put(
|
|||||||
router.delete(
|
router.delete(
|
||||||
'/:workspaceId/environments',
|
'/:workspaceId/environments',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt'],
|
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN],
|
acceptedRoles: [ADMIN],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
body('environmentSlug').exists().trim(),
|
body('environmentSlug').exists().trim(),
|
||||||
@@ -57,10 +64,11 @@ router.delete(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/environments',
|
'/:workspaceId/environments',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt'],
|
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [MEMBER, ADMIN],
|
acceptedRoles: [MEMBER, ADMIN],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import workspace from './workspace';
|
|||||||
import secret from './secret'; // deprecated
|
import secret from './secret'; // deprecated
|
||||||
import secrets from './secrets';
|
import secrets from './secrets';
|
||||||
import serviceTokenData from './serviceTokenData';
|
import serviceTokenData from './serviceTokenData';
|
||||||
|
import serviceAccounts from './serviceAccounts';
|
||||||
import apiKeyData from './apiKeyData';
|
import apiKeyData from './apiKeyData';
|
||||||
import environment from "./environment"
|
import environment from "./environment"
|
||||||
import tags from "./tags"
|
import tags from "./tags"
|
||||||
@@ -19,6 +20,7 @@ export {
|
|||||||
secret,
|
secret,
|
||||||
secrets,
|
secrets,
|
||||||
serviceTokenData,
|
serviceTokenData,
|
||||||
|
serviceAccounts,
|
||||||
apiKeyData,
|
apiKeyData,
|
||||||
environment,
|
environment,
|
||||||
tags
|
tags
|
||||||
|
|||||||
@@ -6,8 +6,15 @@ import {
|
|||||||
requireMembershipOrgAuth,
|
requireMembershipOrgAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { body, param, query } from 'express-validator';
|
import { body, param } from 'express-validator';
|
||||||
import { OWNER, ADMIN, MEMBER, ACCEPTED } from '../../variables';
|
import {
|
||||||
|
OWNER,
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
ACCEPTED,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
|
} from '../../variables';
|
||||||
import { organizationsController } from '../../controllers/v2';
|
import { organizationsController } from '../../controllers/v2';
|
||||||
|
|
||||||
// TODO: /POST to create membership
|
// TODO: /POST to create membership
|
||||||
@@ -17,7 +24,7 @@ router.get(
|
|||||||
param('organizationId').exists().trim(),
|
param('organizationId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
@@ -33,14 +40,15 @@ router.patch(
|
|||||||
body('role').exists().isString().trim().isIn([OWNER, ADMIN, MEMBER]),
|
body('role').exists().isString().trim().isIn([OWNER, ADMIN, MEMBER]),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
requireMembershipOrgAuth({
|
requireMembershipOrgAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN]
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
organizationsController.updateOrganizationMembership
|
organizationsController.updateOrganizationMembership
|
||||||
);
|
);
|
||||||
@@ -51,14 +59,15 @@ router.delete(
|
|||||||
param('membershipId').exists().trim(),
|
param('membershipId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
requireMembershipOrgAuth({
|
requireMembershipOrgAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN]
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
organizationsController.deleteOrganizationMembership
|
organizationsController.deleteOrganizationMembership
|
||||||
);
|
);
|
||||||
@@ -68,7 +77,7 @@ router.get(
|
|||||||
param('organizationId').exists().trim(),
|
param('organizationId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey']
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
@@ -77,4 +86,18 @@ router.get(
|
|||||||
organizationsController.getOrganizationWorkspaces
|
organizationsController.getOrganizationWorkspaces
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:organizationId/service-accounts',
|
||||||
|
param('organizationId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
|
}),
|
||||||
|
organizationsController.getOrganizationServiceAccounts
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@@ -6,7 +6,14 @@ import {
|
|||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { body, param, query } from 'express-validator';
|
import { body, param, query } from 'express-validator';
|
||||||
import { ADMIN, MEMBER } from '../../variables';
|
import {
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
PERMISSION_WRITE_SECRETS
|
||||||
|
} from '../../variables';
|
||||||
import { CreateSecretRequestBody, ModifySecretRequestBody } from '../../types/secret';
|
import { CreateSecretRequestBody, ModifySecretRequestBody } from '../../types/secret';
|
||||||
import { secretController } from '../../controllers/v2';
|
import { secretController } from '../../controllers/v2';
|
||||||
|
|
||||||
@@ -17,10 +24,11 @@ const router = express.Router();
|
|||||||
router.post(
|
router.post(
|
||||||
'/batch-create/workspace/:workspaceId/environment/:environment',
|
'/batch-create/workspace/:workspaceId/environment/:environment',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().isMongoId().trim(),
|
param('workspaceId').exists().isMongoId().trim(),
|
||||||
param('environment').exists().trim(),
|
param('environment').exists().trim(),
|
||||||
@@ -33,10 +41,11 @@ router.post(
|
|||||||
router.post(
|
router.post(
|
||||||
'/workspace/:workspaceId/environment/:environment',
|
'/workspace/:workspaceId/environment/:environment',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().isMongoId().trim(),
|
param('workspaceId').exists().isMongoId().trim(),
|
||||||
param('environment').exists().trim(),
|
param('environment').exists().trim(),
|
||||||
@@ -51,10 +60,11 @@ router.get(
|
|||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
query("environment").exists(),
|
query("environment").exists(),
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'serviceToken']
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
query('channel'),
|
query('channel'),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -64,10 +74,11 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
'/:secretId',
|
'/:secretId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'serviceToken']
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireSecretAuth({
|
requireSecretAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
requiredPermissions: [PERMISSION_READ_SECRETS]
|
||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
secretController.getSecret
|
secretController.getSecret
|
||||||
@@ -76,13 +87,14 @@ router.get(
|
|||||||
router.delete(
|
router.delete(
|
||||||
'/batch/workspace/:workspaceId/environment/:environmentName',
|
'/batch/workspace/:workspaceId/environment/:environmentName',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
param('workspaceId').exists().isMongoId().trim(),
|
param('workspaceId').exists().isMongoId().trim(),
|
||||||
param('environmentName').exists().trim(),
|
param('environmentName').exists().trim(),
|
||||||
body('secretIds').exists().isArray().custom(array => array.length > 0),
|
body('secretIds').exists().isArray().custom(array => array.length > 0),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
secretController.deleteSecrets
|
secretController.deleteSecrets
|
||||||
@@ -91,10 +103,11 @@ router.delete(
|
|||||||
router.delete(
|
router.delete(
|
||||||
'/:secretId',
|
'/:secretId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireSecretAuth({
|
requireSecretAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
requiredPermissions: [PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
param('secretId').isMongoId(),
|
param('secretId').isMongoId(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -104,29 +117,30 @@ router.delete(
|
|||||||
router.patch(
|
router.patch(
|
||||||
'/batch-modify/workspace/:workspaceId/environment/:environmentName',
|
'/batch-modify/workspace/:workspaceId/environment/:environmentName',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
body('secrets').exists().isArray().custom((secrets: ModifySecretRequestBody[]) => secrets.length > 0),
|
body('secrets').exists().isArray().custom((secrets: ModifySecretRequestBody[]) => secrets.length > 0),
|
||||||
param('workspaceId').exists().isMongoId().trim(),
|
param('workspaceId').exists().isMongoId().trim(),
|
||||||
param('environmentName').exists().trim(),
|
param('environmentName').exists().trim(),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
secretController.updateSecrets
|
secretController.updateSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
'/workspace/:workspaceId/environment/:environmentName',
|
'/workspace/:workspaceId/environment/:environmentName',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
body('secret').isObject(),
|
body('secret').isObject(),
|
||||||
param('workspaceId').exists().isMongoId().trim(),
|
param('workspaceId').exists().isMongoId().trim(),
|
||||||
param('environmentName').exists().trim(),
|
param('environmentName').exists().trim(),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'params'
|
||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
secretController.updateSecret
|
secretController.updateSecret
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
requireAuth,
|
requireAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
@@ -8,12 +9,18 @@ import {
|
|||||||
} from '../../middleware';
|
} from '../../middleware';
|
||||||
import { query, body } from 'express-validator';
|
import { query, body } from 'express-validator';
|
||||||
import { secretsController } from '../../controllers/v2';
|
import { secretsController } from '../../controllers/v2';
|
||||||
import { validateSecrets } from '../../helpers/secret';
|
import { validateClientForSecrets } from '../../helpers/secrets';
|
||||||
import {
|
import {
|
||||||
ADMIN,
|
ADMIN,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
SECRET_PERSONAL,
|
SECRET_PERSONAL,
|
||||||
SECRET_SHARED
|
SECRET_SHARED,
|
||||||
|
PERMISSION_READ_SECRETS,
|
||||||
|
PERMISSION_WRITE_SECRETS,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN,
|
||||||
|
AUTH_MODE_API_KEY
|
||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
import {
|
import {
|
||||||
BatchSecretRequest
|
BatchSecretRequest
|
||||||
@@ -22,12 +29,11 @@ import {
|
|||||||
router.post(
|
router.post(
|
||||||
'/batch',
|
'/batch',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN]
|
||||||
requiredServiceTokenPermissions: ['read', 'write']
|
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
location: 'body'
|
locationWorkspaceId: 'body'
|
||||||
}),
|
}),
|
||||||
body('workspaceId').exists().isString().trim(),
|
body('workspaceId').exists().isString().trim(),
|
||||||
body('environment').exists().isString().trim(),
|
body('environment').exists().isString().trim(),
|
||||||
@@ -40,12 +46,11 @@ router.post(
|
|||||||
.filter((secretId) => secretId !== undefined)
|
.filter((secretId) => secretId !== undefined)
|
||||||
|
|
||||||
if (secretIds.length > 0) {
|
if (secretIds.length > 0) {
|
||||||
const relevantSecrets = await validateSecrets({
|
req.secrets = await validateClientForSecrets({
|
||||||
userId: req.user._id.toString(),
|
authData: req.authData,
|
||||||
secretIds
|
secretIds: secretIds.map((secretId: string) => new Types.ObjectId(secretId)),
|
||||||
|
requiredPermissions: []
|
||||||
});
|
});
|
||||||
|
|
||||||
req.secrets = relevantSecrets;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -100,12 +105,13 @@ router.post(
|
|||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN]
|
||||||
requiredServiceTokenPermissions: ['write']
|
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
location: 'body'
|
locationWorkspaceId: 'body',
|
||||||
|
locationEnvironment: 'body',
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
secretsController.createSecrets
|
secretsController.createSecrets
|
||||||
);
|
);
|
||||||
@@ -117,12 +123,13 @@ router.get(
|
|||||||
query('tagSlugs'),
|
query('tagSlugs'),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN]
|
||||||
requiredServiceTokenPermissions: ['read']
|
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
location: 'query'
|
locationWorkspaceId: 'query',
|
||||||
|
locationEnvironment: 'query',
|
||||||
|
requiredPermissions: [PERMISSION_READ_SECRETS]
|
||||||
}),
|
}),
|
||||||
secretsController.getSecrets
|
secretsController.getSecrets
|
||||||
);
|
);
|
||||||
@@ -157,11 +164,11 @@ router.patch(
|
|||||||
}),
|
}),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN]
|
||||||
requiredServiceTokenPermissions: ['write']
|
|
||||||
}),
|
}),
|
||||||
requireSecretsAuth({
|
requireSecretsAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
secretsController.updateSecrets
|
secretsController.updateSecrets
|
||||||
);
|
);
|
||||||
@@ -186,14 +193,13 @@ router.delete(
|
|||||||
.isEmpty(),
|
.isEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY, AUTH_MODE_SERVICE_TOKEN]
|
||||||
requiredServiceTokenPermissions: ['write']
|
|
||||||
}),
|
}),
|
||||||
requireSecretsAuth({
|
requireSecretsAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
secretsController.deleteSecrets
|
secretsController.deleteSecrets
|
||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import {
|
||||||
|
requireAuth,
|
||||||
|
requireOrganizationAuth,
|
||||||
|
requireWorkspaceAuth,
|
||||||
|
requireServiceAccountAuth,
|
||||||
|
requireServiceAccountWorkspacePermissionAuth,
|
||||||
|
validateRequest
|
||||||
|
} from '../../middleware';
|
||||||
|
import { param, query, body } from 'express-validator';
|
||||||
|
import {
|
||||||
|
OWNER,
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
ACCEPTED,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT
|
||||||
|
} from '../../variables';
|
||||||
|
import { serviceAccountsController } from '../../controllers/v2';
|
||||||
|
|
||||||
|
router.get( // TODO: check
|
||||||
|
'/me',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_SERVICE_ACCOUNT]
|
||||||
|
}),
|
||||||
|
serviceAccountsController.getCurrentServiceAccount
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:serviceAccountId',
|
||||||
|
param('serviceAccountId').exists().isString().trim(),
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
requireServiceAccountAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
|
}),
|
||||||
|
serviceAccountsController.getServiceAccountById
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/',
|
||||||
|
body('organizationId').exists().isString().trim(),
|
||||||
|
body('name').exists().isString().trim(),
|
||||||
|
body('publicKey').exists().isString().trim(),
|
||||||
|
body('expiresIn').isNumeric(), // measured in ms
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
requireOrganizationAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [ACCEPTED],
|
||||||
|
locationOrganizationId: 'body'
|
||||||
|
}),
|
||||||
|
serviceAccountsController.createServiceAccount
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
'/:serviceAccountId/name',
|
||||||
|
param('serviceAccountId').exists().isString().trim(),
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
requireServiceAccountAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
|
}),
|
||||||
|
serviceAccountsController.changeServiceAccountName
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
'/:serviceAccountId',
|
||||||
|
param('serviceAccountId').exists().isString().trim(),
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
requireServiceAccountAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
|
}),
|
||||||
|
serviceAccountsController.deleteServiceAccount
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:serviceAccountId/permissions/workspace',
|
||||||
|
param('serviceAccountId').exists().isString().trim(),
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
requireServiceAccountAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
|
}),
|
||||||
|
serviceAccountsController.getServiceAccountWorkspacePermissions
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/:serviceAccountId/permissions/workspace',
|
||||||
|
param('serviceAccountId').exists().isString().trim(),
|
||||||
|
body('workspaceId').exists().isString().notEmpty(),
|
||||||
|
body('environment').exists().isString().notEmpty(),
|
||||||
|
body('read').isBoolean().optional(),
|
||||||
|
body('write').isBoolean().optional(),
|
||||||
|
body('encryptedKey').exists().isString().notEmpty(),
|
||||||
|
body('nonce').exists().isString().notEmpty(),
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
requireServiceAccountAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
locationWorkspaceId: 'body'
|
||||||
|
}),
|
||||||
|
serviceAccountsController.addServiceAccountWorkspacePermission
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
'/:serviceAccountId/permissions/workspace/:serviceAccountWorkspacePermissionId',
|
||||||
|
param('serviceAccountId').exists().isString().trim(),
|
||||||
|
param('serviceAccountWorkspacePermissionId').exists().isString().trim(),
|
||||||
|
validateRequest,
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
requireServiceAccountAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
|
}),
|
||||||
|
requireServiceAccountWorkspacePermissionAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
|
}),
|
||||||
|
serviceAccountsController.deleteServiceAccountWorkspacePermission
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:serviceAccountId/keys',
|
||||||
|
query('workspaceId').optional().isString(),
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT]
|
||||||
|
}),
|
||||||
|
requireServiceAccountAuth({
|
||||||
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
|
acceptedStatuses: [ACCEPTED]
|
||||||
|
}),
|
||||||
|
serviceAccountsController.getServiceAccountKeys
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -10,13 +10,17 @@ import { param, body } from 'express-validator';
|
|||||||
import {
|
import {
|
||||||
ADMIN,
|
ADMIN,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
|
PERMISSION_WRITE_SECRETS,
|
||||||
|
AUTH_MODE_JWT,
|
||||||
|
AUTH_MODE_SERVICE_ACCOUNT,
|
||||||
|
AUTH_MODE_SERVICE_TOKEN
|
||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
import { serviceTokenDataController } from '../../controllers/v2';
|
import { serviceTokenDataController } from '../../controllers/v2';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['serviceToken']
|
acceptedAuthModes: [AUTH_MODE_SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
serviceTokenDataController.getServiceTokenData
|
serviceTokenDataController.getServiceTokenData
|
||||||
);
|
);
|
||||||
@@ -24,11 +28,13 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT]
|
||||||
}),
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
location: 'body'
|
locationWorkspaceId: 'body',
|
||||||
|
locationEnvironment: 'body',
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
}),
|
}),
|
||||||
body('name').exists().isString().trim(),
|
body('name').exists().isString().trim(),
|
||||||
body('workspaceId').exists().isString().trim(),
|
body('workspaceId').exists().isString().trim(),
|
||||||
@@ -53,7 +59,7 @@ router.post(
|
|||||||
router.delete(
|
router.delete(
|
||||||
'/:serviceTokenDataId',
|
'/:serviceTokenDataId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: ['jwt']
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
}),
|
}),
|
||||||
requireServiceTokenDataAuth({
|
requireServiceTokenDataAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user