Compare commits
44 Commits
add-loadin
...
update-sam
Author | SHA1 | Date | |
---|---|---|---|
![]() |
934a525898 | ||
![]() |
40e10cc270 | ||
![]() |
41db227eb3 | ||
![]() |
43eea965c5 | ||
![]() |
8101c9f0bc | ||
![]() |
b4cda90338 | ||
![]() |
7ca37c412e | ||
![]() |
e4e3356dc9 | ||
![]() |
0deaa03218 | ||
![]() |
a7104c41a2 | ||
![]() |
5176b8c322 | ||
![]() |
c37c70446d | ||
![]() |
63abc8a2c8 | ||
![]() |
ba5c038e3b | ||
![]() |
a6669415f5 | ||
![]() |
4086fad867 | ||
![]() |
8a71c13078 | ||
![]() |
5d77f64e76 | ||
![]() |
0d092b977f | ||
![]() |
69582ff83d | ||
![]() |
a5c7da331a | ||
![]() |
8e842296b7 | ||
![]() |
7db14d1df7 | ||
![]() |
067ec2eb9c | ||
![]() |
2d332b32d9 | ||
![]() |
1d9ad2ba86 | ||
![]() |
a28e2177f7 | ||
![]() |
18fe0df691 | ||
![]() |
8e21a06d99 | ||
![]() |
2daf5473bb | ||
![]() |
928ff53adf | ||
![]() |
a71e95e6e5 | ||
![]() |
cb4a54b5cc | ||
![]() |
37e4524156 | ||
![]() |
9ac24ee051 | ||
![]() |
f28ccd559a | ||
![]() |
8e84a93d8e | ||
![]() |
d871dec1b7 | ||
![]() |
b133e1a197 | ||
![]() |
9346a037b9 | ||
![]() |
89facbcddd | ||
![]() |
53fef35638 | ||
![]() |
bfe496a09b | ||
![]() |
0dd444d50b |
@@ -33,7 +33,32 @@ services:
|
|||||||
- '6379:6379'
|
- '6379:6379'
|
||||||
expose:
|
expose:
|
||||||
- 6379
|
- 6379
|
||||||
|
keycloak:
|
||||||
|
image: quay.io/keycloak/keycloak:21.1
|
||||||
|
restart: always
|
||||||
|
container_name: keycloak
|
||||||
|
environment:
|
||||||
|
- KEYCLOAK_ADMIN=admin
|
||||||
|
- KEYCLOAK_ADMIN_PASSWORD=admin
|
||||||
|
- KC_DB=postgres
|
||||||
|
- KC_DB_URL_HOST=postgres
|
||||||
|
- KC_DB_URL_DATABASE=keycloak
|
||||||
|
- KC_DB_USERNAME=automatisch_user
|
||||||
|
- KC_DB_PASSWORD=automatisch_password
|
||||||
|
- KC_HEALTH_ENABLED=true
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
command: start-dev
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
healthcheck:
|
||||||
|
test: "curl -f http://localhost:8080/health/ready || exit 1"
|
||||||
|
volumes:
|
||||||
|
- keycloak:/opt/keycloak/data/
|
||||||
|
expose:
|
||||||
|
- 8080
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
redis_data:
|
redis_data:
|
||||||
|
keycloak:
|
||||||
|
@@ -4,7 +4,7 @@ WORKDIR /automatisch
|
|||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk --no-cache add --virtual build-dependencies python3 build-base && \
|
apk --no-cache add --virtual build-dependencies python3 build-base && \
|
||||||
yarn global add @automatisch/cli@0.7.1 --network-timeout 1000000 && \
|
yarn global add @automatisch/cli@0.8.0 --network-timeout 1000000 && \
|
||||||
rm -rf /usr/local/share/.cache/ && \
|
rm -rf /usr/local/share/.cache/ && \
|
||||||
apk del build-dependencies
|
apk del build-dependencies
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM automatischio/automatisch:0.7.1
|
FROM automatischio/automatisch:0.8.0
|
||||||
WORKDIR /automatisch
|
WORKDIR /automatisch
|
||||||
|
|
||||||
RUN apk add --no-cache openssl dos2unix
|
RUN apk add --no-cache openssl dos2unix
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"version": "0.7.1",
|
"version": "0.8.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"command": {
|
"command": {
|
||||||
|
@@ -2,18 +2,33 @@ import appConfig from '../../src/config/app';
|
|||||||
import logger from '../../src/helpers/logger';
|
import logger from '../../src/helpers/logger';
|
||||||
import client from './client';
|
import client from './client';
|
||||||
import User from '../../src/models/user';
|
import User from '../../src/models/user';
|
||||||
|
import Role from '../../src/models/role';
|
||||||
import '../../src/config/orm';
|
import '../../src/config/orm';
|
||||||
|
|
||||||
|
async function fetchAdminRole() {
|
||||||
|
const role = await Role
|
||||||
|
.query()
|
||||||
|
.where({
|
||||||
|
key: 'admin'
|
||||||
|
})
|
||||||
|
.limit(1)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
export async function createUser(
|
export async function createUser(
|
||||||
email = 'user@automatisch.io',
|
email = 'user@automatisch.io',
|
||||||
password = 'sample'
|
password = 'sample'
|
||||||
) {
|
) {
|
||||||
const UNIQUE_VIOLATION_CODE = '23505';
|
const UNIQUE_VIOLATION_CODE = '23505';
|
||||||
|
|
||||||
|
const role = await fetchAdminRole();
|
||||||
const userParams = {
|
const userParams = {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
fullName: 'Initial admin',
|
fullName: 'Initial admin',
|
||||||
role: 'admin',
|
roleId: role.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@@ -12,6 +12,7 @@ const knexConfig = {
|
|||||||
database: appConfig.postgresDatabase,
|
database: appConfig.postgresDatabase,
|
||||||
ssl: appConfig.postgresEnableSsl,
|
ssl: appConfig.postgresEnableSsl,
|
||||||
},
|
},
|
||||||
|
asyncStackTraces: appConfig.isDev,
|
||||||
searchPath: [appConfig.postgresSchema],
|
searchPath: [appConfig.postgresSchema],
|
||||||
pool: { min: 0, max: 20 },
|
pool: { min: 0, max: 20 },
|
||||||
migrations: {
|
migrations: {
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/backend",
|
"name": "@automatisch/backend",
|
||||||
"version": "0.7.1",
|
"version": "0.8.0",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "ts-node-dev --exit-child src/server.ts",
|
"dev": "ts-node-dev --watch 'src/graphql/schema.graphql' --exit-child src/server.ts",
|
||||||
"worker": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/worker.ts",
|
"worker": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/worker.ts",
|
||||||
"build": "tsc && yarn copy-statics",
|
"build": "tsc && yarn copy-statics",
|
||||||
"build:watch": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec yarn build --ext ts",
|
"build:watch": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec yarn build --ext ts",
|
||||||
@@ -22,14 +22,17 @@
|
|||||||
"prebuild": "rm -rf ./dist"
|
"prebuild": "rm -rf ./dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automatisch/web": "^0.7.1",
|
"@automatisch/web": "^0.8.0",
|
||||||
"@bull-board/express": "^3.10.1",
|
"@bull-board/express": "^3.10.1",
|
||||||
|
"@casl/ability": "^6.5.0",
|
||||||
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
||||||
"@graphql-tools/load": "^7.5.2",
|
"@graphql-tools/load": "^7.5.2",
|
||||||
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@rudderstack/rudder-sdk-node": "^1.1.2",
|
"@rudderstack/rudder-sdk-node": "^1.1.2",
|
||||||
"@sentry/node": "^7.42.0",
|
"@sentry/node": "^7.42.0",
|
||||||
"@sentry/tracing": "^7.42.0",
|
"@sentry/tracing": "^7.42.0",
|
||||||
"@types/luxon": "^2.3.1",
|
"@types/luxon": "^2.3.1",
|
||||||
|
"@types/passport": "^1.0.12",
|
||||||
"@types/xmlrpc": "^1.3.7",
|
"@types/xmlrpc": "^1.3.7",
|
||||||
"ajv-formats": "^2.1.1",
|
"ajv-formats": "^2.1.1",
|
||||||
"axios": "0.24.0",
|
"axios": "0.24.0",
|
||||||
@@ -50,6 +53,8 @@
|
|||||||
"graphql-type-json": "^0.3.2",
|
"graphql-type-json": "^0.3.2",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"http-errors": "~1.6.3",
|
"http-errors": "~1.6.3",
|
||||||
|
"http-proxy-agent": "^7.0.0",
|
||||||
|
"https-proxy-agent": "^7.0.1",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"knex": "^2.4.0",
|
"knex": "^2.4.0",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
@@ -60,6 +65,7 @@
|
|||||||
"nodemailer": "6.7.0",
|
"nodemailer": "6.7.0",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"objection": "^3.0.0",
|
"objection": "^3.0.0",
|
||||||
|
"passport": "^0.6.0",
|
||||||
"pg": "^8.7.1",
|
"pg": "^8.7.1",
|
||||||
"php-serialize": "^4.0.2",
|
"php-serialize": "^4.0.2",
|
||||||
"stripe": "^11.13.0",
|
"stripe": "^11.13.0",
|
||||||
@@ -102,7 +108,7 @@
|
|||||||
"url": "https://github.com/automatisch/automatisch/issues"
|
"url": "https://github.com/automatisch/automatisch/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@automatisch/types": "^0.7.1",
|
"@automatisch/types": "^0.8.0",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/bull": "^3.15.8",
|
"@types/bull": "^3.15.8",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
|
@@ -17,6 +17,7 @@ import {
|
|||||||
} from './helpers/create-bull-board-handler';
|
} from './helpers/create-bull-board-handler';
|
||||||
import injectBullBoardHandler from './helpers/inject-bull-board-handler';
|
import injectBullBoardHandler from './helpers/inject-bull-board-handler';
|
||||||
import router from './routes';
|
import router from './routes';
|
||||||
|
import configurePassport from './helpers/passport';
|
||||||
|
|
||||||
createBullBoardHandler(serverAdapter);
|
createBullBoardHandler(serverAdapter);
|
||||||
|
|
||||||
@@ -50,6 +51,9 @@ app.use(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
app.use(cors(corsOptions));
|
app.use(cors(corsOptions));
|
||||||
|
|
||||||
|
configurePassport(app);
|
||||||
|
|
||||||
app.use('/', router);
|
app.use('/', router);
|
||||||
|
|
||||||
webUIHandler(app);
|
webUIHandler(app);
|
||||||
|
@@ -6,7 +6,7 @@ import actions from './actions';
|
|||||||
import dynamicData from './dynamic-data';
|
import dynamicData from './dynamic-data';
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
name: 'Github',
|
name: 'GitHub',
|
||||||
key: 'github',
|
key: 'github',
|
||||||
baseUrl: 'https://github.com',
|
baseUrl: 'https://github.com',
|
||||||
apiBaseUrl: 'https://api.github.com',
|
apiBaseUrl: 'https://api.github.com',
|
||||||
|
@@ -9,11 +9,11 @@ export default {
|
|||||||
// ref:
|
// ref:
|
||||||
// - https://docs.gitlab.com/ee/api/projects.html#list-all-projects
|
// - https://docs.gitlab.com/ee/api/projects.html#list-all-projects
|
||||||
// - https://docs.gitlab.com/ee/api/rest/index.html#keyset-based-pagination
|
// - https://docs.gitlab.com/ee/api/rest/index.html#keyset-based-pagination
|
||||||
|
|
||||||
const firstPageRequest = $.http.get('/api/v4/projects', {
|
const firstPageRequest = $.http.get('/api/v4/projects', {
|
||||||
params: {
|
params: {
|
||||||
simple: true,
|
simple: true,
|
||||||
pagination: 'keyset',
|
pagination: 'keyset',
|
||||||
|
membership: true,
|
||||||
order_by: 'id',
|
order_by: 'id',
|
||||||
sort: 'asc',
|
sort: 'asc',
|
||||||
},
|
},
|
||||||
|
@@ -6,7 +6,7 @@ import triggers from './triggers';
|
|||||||
import dynamicData from './dynamic-data';
|
import dynamicData from './dynamic-data';
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
name: 'Gitlab',
|
name: 'GitLab',
|
||||||
key: 'gitlab',
|
key: 'gitlab',
|
||||||
baseUrl: 'https://gitlab.com',
|
baseUrl: 'https://gitlab.com',
|
||||||
apiBaseUrl: 'https://gitlab.com',
|
apiBaseUrl: 'https://gitlab.com',
|
||||||
|
@@ -0,0 +1,191 @@
|
|||||||
|
import { IJSONObject } from '@automatisch/types';
|
||||||
|
import defineAction from '../../../../helpers/define-action';
|
||||||
|
|
||||||
|
type THeaders = {
|
||||||
|
__id: string;
|
||||||
|
header: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
type TSheetsResponse = {
|
||||||
|
sheets: {
|
||||||
|
properties: {
|
||||||
|
sheetId: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type TBody = {
|
||||||
|
requests: IJSONObject[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create worksheet',
|
||||||
|
key: 'createWorksheet',
|
||||||
|
description:
|
||||||
|
'Create a blank worksheet with a title. Optionally, provide headers.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Drive',
|
||||||
|
key: 'driveId',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'The Google Drive where your spreadsheet resides. If nothing is selected, then your personal Google Drive will be used.',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listDrives',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Spreadsheet',
|
||||||
|
key: 'spreadsheetId',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.driveId'],
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSpreadsheets',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.driveId',
|
||||||
|
value: '{parameters.driveId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Title',
|
||||||
|
key: 'title',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Headers',
|
||||||
|
key: 'headers',
|
||||||
|
type: 'dynamic' as const,
|
||||||
|
required: false,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Header',
|
||||||
|
key: 'header',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Overwrite',
|
||||||
|
key: 'overwrite',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: false,
|
||||||
|
value: false,
|
||||||
|
description:
|
||||||
|
'If a worksheet with the specified title exists, its content would be lost. Please, use with caution.',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Yes',
|
||||||
|
value: 'true',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'No',
|
||||||
|
value: 'false',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const {
|
||||||
|
data: { sheets },
|
||||||
|
} = await $.http.get<TSheetsResponse>(
|
||||||
|
`/v4/spreadsheets/${$.step.parameters.spreadsheetId}`
|
||||||
|
);
|
||||||
|
const selectedSheet = sheets.find(
|
||||||
|
(sheet) => sheet.properties.title === $.step.parameters.title
|
||||||
|
);
|
||||||
|
const headers = $.step.parameters.headers as THeaders;
|
||||||
|
const values = headers.map((entry) => entry.header);
|
||||||
|
|
||||||
|
const body: TBody = {
|
||||||
|
requests: [
|
||||||
|
{
|
||||||
|
addSheet: {
|
||||||
|
properties: {
|
||||||
|
title: $.step.parameters.title,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($.step.parameters.overwrite === 'true' && selectedSheet) {
|
||||||
|
body.requests.unshift({
|
||||||
|
deleteSheet: {
|
||||||
|
sheetId: selectedSheet.properties.sheetId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`https://sheets.googleapis.com/v4/spreadsheets/${$.step.parameters.spreadsheetId}:batchUpdate`,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
|
||||||
|
if (values.length) {
|
||||||
|
const body = {
|
||||||
|
requests: [
|
||||||
|
{
|
||||||
|
updateCells: {
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
values: values.map((header) => ({
|
||||||
|
userEnteredValue: { stringValue: header },
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fields: '*',
|
||||||
|
start: {
|
||||||
|
sheetId:
|
||||||
|
data.replies[data.replies.length - 1].addSheet.properties
|
||||||
|
.sheetId,
|
||||||
|
rowIndex: 0,
|
||||||
|
columnIndex: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data: response } = await $.http.post(
|
||||||
|
`https://sheets.googleapis.com/v4/spreadsheets/${$.step.parameters.spreadsheetId}:batchUpdate`,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: response,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@@ -1,4 +1,5 @@
|
|||||||
import createSpreadsheet from './create-spreadsheet';
|
import createSpreadsheet from './create-spreadsheet';
|
||||||
import createSpreadsheetRow from './create-spreadsheet-row';
|
import createSpreadsheetRow from './create-spreadsheet-row';
|
||||||
|
import createWorksheet from './create-worksheet';
|
||||||
|
|
||||||
export default [createSpreadsheet, createSpreadsheetRow];
|
export default [createSpreadsheet, createSpreadsheetRow, createWorksheet];
|
||||||
|
@@ -0,0 +1,100 @@
|
|||||||
|
import { IJSONArray, IJSONObject } from '@automatisch/types';
|
||||||
|
import defineAction from '../../../../helpers/define-action';
|
||||||
|
|
||||||
|
type TBody = {
|
||||||
|
parent: IJSONObject;
|
||||||
|
properties: IJSONObject;
|
||||||
|
children: IJSONArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create database item',
|
||||||
|
key: 'createDatabaseItem',
|
||||||
|
description: 'Creates an item in a database.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Database',
|
||||||
|
key: 'databaseId',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listDatabases',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Name',
|
||||||
|
key: 'name',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'This field has a 2000 character limit. Any characters beyond 2000 will not be included.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Content',
|
||||||
|
key: 'content',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'The text to add to the page body. The max length for this field is 2000 characters. Any characters beyond 2000 will not be included.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const name = $.step.parameters.name as string;
|
||||||
|
const truncatedName = name.slice(0, 2000);
|
||||||
|
const content = $.step.parameters.content as string;
|
||||||
|
const truncatedContent = content.slice(0, 2000);
|
||||||
|
|
||||||
|
const body: TBody = {
|
||||||
|
parent: {
|
||||||
|
database_id: $.step.parameters.databaseId,
|
||||||
|
},
|
||||||
|
properties: {},
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
body.properties.Name = {
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: truncatedName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content) {
|
||||||
|
body.children = [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
paragraph: {
|
||||||
|
rich_text: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: truncatedContent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.post('/v1/pages', body);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
104
packages/backend/src/apps/notion/actions/create-page/index.ts
Normal file
104
packages/backend/src/apps/notion/actions/create-page/index.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { IJSONArray, IJSONObject } from '@automatisch/types';
|
||||||
|
import defineAction from '../../../../helpers/define-action';
|
||||||
|
|
||||||
|
type TBody = {
|
||||||
|
parent: IJSONObject;
|
||||||
|
properties: IJSONObject;
|
||||||
|
children: IJSONArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create page',
|
||||||
|
key: 'createPage',
|
||||||
|
description: 'Creates a page inside a parent page',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Parent page',
|
||||||
|
key: 'parentPageId',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listParentPages',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Title',
|
||||||
|
key: 'title',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'This field has a 2000 character limit. Any characters beyond 2000 will not be included.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Content',
|
||||||
|
key: 'content',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'The text to add to the page body. The max length for this field is 2000 characters. Any characters beyond 2000 will not be included.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const parentPageId = $.step.parameters.parentPageId as string;
|
||||||
|
const title = $.step.parameters.title as string;
|
||||||
|
const truncatedTitle = title.slice(0, 2000);
|
||||||
|
const content = $.step.parameters.content as string;
|
||||||
|
const truncatedContent = content.slice(0, 2000);
|
||||||
|
|
||||||
|
const body: TBody = {
|
||||||
|
parent: {
|
||||||
|
page_id: parentPageId,
|
||||||
|
},
|
||||||
|
properties: {},
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
body.properties.title = {
|
||||||
|
type: 'title',
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: truncatedTitle,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content) {
|
||||||
|
body.children = [
|
||||||
|
{
|
||||||
|
object: 'block',
|
||||||
|
type: 'paragraph',
|
||||||
|
paragraph: {
|
||||||
|
rich_text: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: {
|
||||||
|
content: truncatedContent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.post('/v1/pages', body);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@@ -0,0 +1,70 @@
|
|||||||
|
import { IJSONArray, IJSONObject } from '@automatisch/types';
|
||||||
|
import defineAction from '../../../../helpers/define-action';
|
||||||
|
|
||||||
|
type TBody = {
|
||||||
|
filter: IJSONObject;
|
||||||
|
sorts: IJSONArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Find database item',
|
||||||
|
key: 'findDatabaseItem',
|
||||||
|
description: 'Searches for an item in a database by property.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Database',
|
||||||
|
key: 'databaseId',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listDatabases',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Name',
|
||||||
|
key: 'name',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'This field has a 2000 character limit. Any characters beyond 2000 will not be included.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const databaseId = $.step.parameters.databaseId as string;
|
||||||
|
const name = $.step.parameters.name as string;
|
||||||
|
const truncatedName = name.slice(0, 2000);
|
||||||
|
|
||||||
|
const body: TBody = {
|
||||||
|
filter: {
|
||||||
|
property: 'Name',
|
||||||
|
rich_text: {
|
||||||
|
equals: truncatedName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sorts: [
|
||||||
|
{
|
||||||
|
timestamp: 'last_edited_time',
|
||||||
|
direction: 'descending',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`/v1/databases/${databaseId}/query`,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: data.results[0],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
5
packages/backend/src/apps/notion/actions/index.ts
Normal file
5
packages/backend/src/apps/notion/actions/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import createDatabaseItem from './create-database-item';
|
||||||
|
import createPage from './create-page';
|
||||||
|
import findDatabaseItem from './find-database-item';
|
||||||
|
|
||||||
|
export default [createDatabaseItem, createPage, findDatabaseItem];
|
@@ -1,3 +1,4 @@
|
|||||||
import listDatabases from './list-databases';
|
import listDatabases from './list-databases';
|
||||||
|
import listParentPages from './list-parent-pages';
|
||||||
|
|
||||||
export default [listDatabases];
|
export default [listDatabases, listParentPages];
|
||||||
|
@@ -0,0 +1,70 @@
|
|||||||
|
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
type Page = {
|
||||||
|
id: string;
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
plain_text: string;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
parent: {
|
||||||
|
workspace: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type ResponseData = {
|
||||||
|
results: Page[];
|
||||||
|
next_cursor?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Payload = {
|
||||||
|
filter: {
|
||||||
|
value: 'page';
|
||||||
|
property: 'object';
|
||||||
|
};
|
||||||
|
start_cursor?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'List parent pages',
|
||||||
|
key: 'listParentPages',
|
||||||
|
|
||||||
|
async run($: IGlobalVariable) {
|
||||||
|
const parentPages: {
|
||||||
|
data: IJSONObject[];
|
||||||
|
error: IJSONObject | null;
|
||||||
|
} = {
|
||||||
|
data: [],
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
const payload: Payload = {
|
||||||
|
filter: {
|
||||||
|
value: 'page',
|
||||||
|
property: 'object',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const response = await $.http.post<ResponseData>('/v1/search', payload);
|
||||||
|
|
||||||
|
payload.start_cursor = response.data.next_cursor;
|
||||||
|
|
||||||
|
const topLevelPages = response.data.results.filter(
|
||||||
|
(page) => page.parent.workspace
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const pages of topLevelPages) {
|
||||||
|
parentPages.data.push({
|
||||||
|
value: pages.id as string,
|
||||||
|
name: pages.properties.title.title[0].plain_text as string,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} while (payload.start_cursor);
|
||||||
|
|
||||||
|
return parentPages;
|
||||||
|
},
|
||||||
|
};
|
@@ -3,6 +3,7 @@ import addAuthHeader from './common/add-auth-header';
|
|||||||
import addNotionVersionHeader from './common/add-notion-version-header';
|
import addNotionVersionHeader from './common/add-notion-version-header';
|
||||||
import auth from './auth';
|
import auth from './auth';
|
||||||
import triggers from './triggers';
|
import triggers from './triggers';
|
||||||
|
import actions from './actions';
|
||||||
import dynamicData from './dynamic-data';
|
import dynamicData from './dynamic-data';
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
@@ -14,11 +15,9 @@ export default defineApp({
|
|||||||
authDocUrl: 'https://automatisch.io/docs/apps/notion/connection',
|
authDocUrl: 'https://automatisch.io/docs/apps/notion/connection',
|
||||||
primaryColor: '000000',
|
primaryColor: '000000',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [
|
beforeRequest: [addAuthHeader, addNotionVersionHeader],
|
||||||
addAuthHeader,
|
|
||||||
addNotionVersionHeader,
|
|
||||||
],
|
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
actions,
|
||||||
dynamicData,
|
dynamicData,
|
||||||
});
|
});
|
||||||
|
@@ -0,0 +1,46 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import capitalize from 'lodash/capitalize';
|
||||||
|
import lowerCase from 'lodash/lowerCase';
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.createTable('roles', (table) => {
|
||||||
|
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
||||||
|
table.string('name').notNullable();
|
||||||
|
table.string('key').notNullable();
|
||||||
|
table.string('description');
|
||||||
|
|
||||||
|
table.timestamps(true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const uniqueUserRoles = await knex('users')
|
||||||
|
.select('role')
|
||||||
|
.groupBy('role');
|
||||||
|
|
||||||
|
let shouldCreateAdminRole = true;
|
||||||
|
for (const { role } of uniqueUserRoles) {
|
||||||
|
// skip empty roles
|
||||||
|
if (!role) continue;
|
||||||
|
|
||||||
|
const lowerCaseRole = lowerCase(role);
|
||||||
|
|
||||||
|
if (lowerCaseRole === 'admin') {
|
||||||
|
shouldCreateAdminRole = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await knex('roles').insert({
|
||||||
|
name: capitalize(role),
|
||||||
|
key: lowerCaseRole,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldCreateAdminRole) {
|
||||||
|
await knex('roles').insert({
|
||||||
|
name: 'Admin',
|
||||||
|
key: 'admin',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
return knex.schema.dropTable('roles');
|
||||||
|
}
|
@@ -0,0 +1,48 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
const getPermissionForRole = (roleId: string, subject: string, actions: string[], conditions: string[] = []) => actions
|
||||||
|
.map(action => ({
|
||||||
|
role_id: roleId,
|
||||||
|
subject,
|
||||||
|
action,
|
||||||
|
conditions,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.createTable('permissions', (table) => {
|
||||||
|
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
||||||
|
table.uuid('role_id').references('id').inTable('roles');
|
||||||
|
table.string('action').notNullable();
|
||||||
|
table.string('subject').notNullable();
|
||||||
|
table.jsonb('conditions').notNullable().defaultTo([]);
|
||||||
|
|
||||||
|
table.timestamps(true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const roles = await knex('roles').select(['id', 'key']) as { id: string, key: string }[];
|
||||||
|
|
||||||
|
for (const role of roles) {
|
||||||
|
// `admin` role should have no conditions unlike others by default
|
||||||
|
const isAdmin = role.key === 'admin';
|
||||||
|
const roleConditions = isAdmin ? [] : ['isCreator'];
|
||||||
|
|
||||||
|
// default permissions
|
||||||
|
await knex('permissions').insert([
|
||||||
|
...getPermissionForRole(role.id, 'Connection', ['create', 'read', 'delete', 'update'], roleConditions),
|
||||||
|
...getPermissionForRole(role.id, 'Execution', ['read'], roleConditions),
|
||||||
|
...getPermissionForRole(role.id, 'Flow', ['create', 'delete', 'publish', 'read', 'update'], roleConditions),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// admin specific permission
|
||||||
|
if (isAdmin) {
|
||||||
|
await knex('permissions').insert([
|
||||||
|
...getPermissionForRole(role.id, 'User', ['create', 'read', 'delete', 'update']),
|
||||||
|
...getPermissionForRole(role.id, 'Role', ['create', 'read', 'delete', 'update']),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
return knex.schema.dropTable('permissions');
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.table('users', async (table) => {
|
||||||
|
table.uuid('role_id').references('id').inTable('roles');
|
||||||
|
});
|
||||||
|
|
||||||
|
const theRole = await knex('roles').select('id').limit(1).first();
|
||||||
|
const roles = await knex('roles').select('id', 'key');
|
||||||
|
|
||||||
|
for (const role of roles) {
|
||||||
|
await knex('users')
|
||||||
|
.where({
|
||||||
|
role: role.key
|
||||||
|
})
|
||||||
|
.update({
|
||||||
|
role_id: role.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// backfill not-migratables
|
||||||
|
await knex('users').whereNull('role_id').update({ role_id: theRole.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
return await knex.schema.table('users', (table) => {
|
||||||
|
table.dropColumn('role_id');
|
||||||
|
});
|
||||||
|
}
|
@@ -0,0 +1,13 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.table('users', async (table) => {
|
||||||
|
table.dropColumn('role');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
return await knex.schema.table('users', (table) => {
|
||||||
|
table.string('role').defaultTo('user');
|
||||||
|
});
|
||||||
|
}
|
@@ -0,0 +1,24 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
return knex.schema.createTable('saml_auth_providers', (table) => {
|
||||||
|
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
||||||
|
table.string('name').notNullable();
|
||||||
|
table.text('certificate').notNullable();
|
||||||
|
table.string('signature_algorithm').notNullable();
|
||||||
|
table.string('issuer').notNullable();
|
||||||
|
table.text('entry_point').notNullable();
|
||||||
|
table.text('firstname_attribute_name').notNullable();
|
||||||
|
table.text('surname_attribute_name').notNullable();
|
||||||
|
table.text('email_attribute_name').notNullable();
|
||||||
|
table.text('role_attribute_name').notNullable();
|
||||||
|
table.uuid('default_role_id').references('id').inTable('roles');
|
||||||
|
table.boolean('active').defaultTo(false);
|
||||||
|
|
||||||
|
table.timestamps(true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
return knex.schema.dropTable('saml_auth_providers');
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
return knex.schema.createTable('identities', (table) => {
|
||||||
|
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
||||||
|
table.uuid('user_id').references('id').inTable('users');
|
||||||
|
table.string('remote_id').notNullable();
|
||||||
|
table.string('provider_id').notNullable();
|
||||||
|
table.string('provider_type').notNullable();
|
||||||
|
|
||||||
|
table.timestamps(true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
return knex.schema.dropTable('identities');
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
return await knex.schema.alterTable('users', (table) => {
|
||||||
|
table.string('password').nullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(): Promise<void> {
|
||||||
|
// void
|
||||||
|
}
|
@@ -0,0 +1,33 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
const getPermissionForRole = (
|
||||||
|
roleId: string,
|
||||||
|
subject: string,
|
||||||
|
actions: string[]
|
||||||
|
) =>
|
||||||
|
actions.map((action) => ({
|
||||||
|
role_id: roleId,
|
||||||
|
subject,
|
||||||
|
action,
|
||||||
|
conditions: [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const role = (await knex('roles')
|
||||||
|
.first(['id', 'key'])
|
||||||
|
.where({ key: 'admin' })
|
||||||
|
.limit(1)) as { id: string; key: string };
|
||||||
|
|
||||||
|
await knex('permissions').insert(
|
||||||
|
getPermissionForRole(role.id, 'SamlAuthProvider', [
|
||||||
|
'create',
|
||||||
|
'read',
|
||||||
|
'delete',
|
||||||
|
'update',
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex('permissions').where({ subject: 'SamlAuthProvider' }).delete();
|
||||||
|
}
|
@@ -1,47 +1,63 @@
|
|||||||
import createConnection from './mutations/create-connection';
|
import createConnection from './mutations/create-connection';
|
||||||
import generateAuthUrl from './mutations/generate-auth-url';
|
|
||||||
import updateConnection from './mutations/update-connection';
|
|
||||||
import resetConnection from './mutations/reset-connection';
|
|
||||||
import verifyConnection from './mutations/verify-connection';
|
|
||||||
import deleteConnection from './mutations/delete-connection';
|
|
||||||
import createFlow from './mutations/create-flow';
|
import createFlow from './mutations/create-flow';
|
||||||
|
import createRole from './mutations/create-role.ee';
|
||||||
|
import createStep from './mutations/create-step';
|
||||||
|
import createUser from './mutations/create-user.ee';
|
||||||
|
import deleteConnection from './mutations/delete-connection';
|
||||||
|
import deleteCurrentUser from './mutations/delete-current-user.ee';
|
||||||
|
import deleteFlow from './mutations/delete-flow';
|
||||||
|
import deleteRole from './mutations/delete-role.ee';
|
||||||
|
import deleteStep from './mutations/delete-step';
|
||||||
|
import deleteUser from './mutations/delete-user.ee';
|
||||||
|
import duplicateFlow from './mutations/duplicate-flow';
|
||||||
|
import executeFlow from './mutations/execute-flow';
|
||||||
|
import forgotPassword from './mutations/forgot-password.ee';
|
||||||
|
import generateAuthUrl from './mutations/generate-auth-url';
|
||||||
|
import login from './mutations/login';
|
||||||
|
import registerUser from './mutations/register-user.ee';
|
||||||
|
import resetConnection from './mutations/reset-connection';
|
||||||
|
import resetPassword from './mutations/reset-password.ee';
|
||||||
|
import updateConnection from './mutations/update-connection';
|
||||||
|
import updateCurrentUser from './mutations/update-current-user';
|
||||||
import updateFlow from './mutations/update-flow';
|
import updateFlow from './mutations/update-flow';
|
||||||
import updateFlowStatus from './mutations/update-flow-status';
|
import updateFlowStatus from './mutations/update-flow-status';
|
||||||
import executeFlow from './mutations/execute-flow';
|
import updateRole from './mutations/update-role.ee';
|
||||||
import deleteFlow from './mutations/delete-flow';
|
|
||||||
import duplicateFlow from './mutations/duplicate-flow';
|
|
||||||
import createStep from './mutations/create-step';
|
|
||||||
import updateStep from './mutations/update-step';
|
import updateStep from './mutations/update-step';
|
||||||
import deleteStep from './mutations/delete-step';
|
import updateUser from './mutations/update-user.ee';
|
||||||
import createUser from './mutations/create-user.ee';
|
import verifyConnection from './mutations/verify-connection';
|
||||||
import deleteUser from './mutations/delete-user.ee';
|
import createSamlAuthProvider from './mutations/create-saml-auth-provider.ee';
|
||||||
import updateUser from './mutations/update-user';
|
import updateSamlAuthProvider from './mutations/update-saml-auth-provider.ee';
|
||||||
import forgotPassword from './mutations/forgot-password.ee';
|
|
||||||
import resetPassword from './mutations/reset-password.ee';
|
|
||||||
import login from './mutations/login';
|
|
||||||
|
|
||||||
const mutationResolvers = {
|
const mutationResolvers = {
|
||||||
createConnection,
|
createConnection,
|
||||||
generateAuthUrl,
|
|
||||||
updateConnection,
|
|
||||||
resetConnection,
|
|
||||||
verifyConnection,
|
|
||||||
deleteConnection,
|
|
||||||
createFlow,
|
createFlow,
|
||||||
|
createRole,
|
||||||
|
createStep,
|
||||||
|
createUser,
|
||||||
|
deleteConnection,
|
||||||
|
deleteCurrentUser,
|
||||||
|
deleteFlow,
|
||||||
|
deleteRole,
|
||||||
|
deleteStep,
|
||||||
|
deleteUser,
|
||||||
|
duplicateFlow,
|
||||||
|
executeFlow,
|
||||||
|
forgotPassword,
|
||||||
|
generateAuthUrl,
|
||||||
|
login,
|
||||||
|
registerUser,
|
||||||
|
resetConnection,
|
||||||
|
resetPassword,
|
||||||
|
updateConnection,
|
||||||
|
updateCurrentUser,
|
||||||
|
updateUser,
|
||||||
updateFlow,
|
updateFlow,
|
||||||
updateFlowStatus,
|
updateFlowStatus,
|
||||||
executeFlow,
|
updateRole,
|
||||||
deleteFlow,
|
|
||||||
duplicateFlow,
|
|
||||||
createStep,
|
|
||||||
updateStep,
|
updateStep,
|
||||||
deleteStep,
|
verifyConnection,
|
||||||
createUser,
|
createSamlAuthProvider,
|
||||||
deleteUser,
|
updateSamlAuthProvider,
|
||||||
updateUser,
|
|
||||||
forgotPassword,
|
|
||||||
resetPassword,
|
|
||||||
login,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default mutationResolvers;
|
export default mutationResolvers;
|
||||||
|
@@ -13,6 +13,8 @@ const createConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
await App.findOneByKey(params.input.key);
|
await App.findOneByKey(params.input.key);
|
||||||
|
|
||||||
return await context.currentUser.$relatedQuery('connections').insert({
|
return await context.currentUser.$relatedQuery('connections').insert({
|
||||||
|
@@ -14,6 +14,8 @@ const createFlow = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Flow');
|
||||||
|
|
||||||
const connectionId = params?.input?.connectionId;
|
const connectionId = params?.input?.connectionId;
|
||||||
const appKey = params?.input?.triggerAppKey;
|
const appKey = params?.input?.triggerAppKey;
|
||||||
|
|
||||||
|
34
packages/backend/src/graphql/mutations/create-role.ee.ts
Normal file
34
packages/backend/src/graphql/mutations/create-role.ee.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import kebabCase from 'lodash/kebabCase';
|
||||||
|
import Permission from '../../models/permission';
|
||||||
|
import Role from '../../models/role';
|
||||||
|
import Context from '../../types/express/context';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
input: {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
permissions: Permission[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createRole = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
|
context.currentUser.can('create', 'Role');
|
||||||
|
|
||||||
|
const { name, description, permissions } = params.input;
|
||||||
|
const key = kebabCase(name);
|
||||||
|
|
||||||
|
const existingRole = await Role.query().findOne({ key });
|
||||||
|
|
||||||
|
if (existingRole) {
|
||||||
|
throw new Error('Role already exists!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Role.query().insertGraph({
|
||||||
|
key,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
permissions,
|
||||||
|
}, { relate: ['permissions'] }).returning('*');
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createRole;
|
@@ -0,0 +1,54 @@
|
|||||||
|
import type { SamlConfig } from '@node-saml/passport-saml';
|
||||||
|
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
|
||||||
|
import Context from '../../types/express/context';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
input: {
|
||||||
|
name: string;
|
||||||
|
certificate: string;
|
||||||
|
signatureAlgorithm: SamlConfig['signatureAlgorithm'];
|
||||||
|
issuer: string;
|
||||||
|
entryPoint: string;
|
||||||
|
firstnameAttributeName: string;
|
||||||
|
surnameAttributeName: string;
|
||||||
|
emailAttributeName: string;
|
||||||
|
roleAttributeName: string;
|
||||||
|
defaultRoleId: string;
|
||||||
|
active: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createSamlAuthProvider = async (
|
||||||
|
_parent: unknown,
|
||||||
|
params: Params,
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
|
context.currentUser.can('create', 'SamlAuthProvider');
|
||||||
|
|
||||||
|
const samlAuthProviderPayload: Partial<SamlAuthProvider> = {
|
||||||
|
...params.input,
|
||||||
|
};
|
||||||
|
|
||||||
|
const existingSamlAuthProvider = await SamlAuthProvider.query()
|
||||||
|
.limit(1)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
let samlAuthProvider: SamlAuthProvider;
|
||||||
|
|
||||||
|
if (!existingSamlAuthProvider) {
|
||||||
|
samlAuthProvider = await SamlAuthProvider.query().insert(
|
||||||
|
samlAuthProviderPayload
|
||||||
|
);
|
||||||
|
|
||||||
|
return samlAuthProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
samlAuthProvider = await SamlAuthProvider.query().patchAndFetchById(
|
||||||
|
existingSamlAuthProvider.id,
|
||||||
|
samlAuthProviderPayload
|
||||||
|
);
|
||||||
|
|
||||||
|
return samlAuthProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createSamlAuthProvider;
|
@@ -22,6 +22,8 @@ const createStep = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
const { input } = params;
|
const { input } = params;
|
||||||
|
|
||||||
if (input.appKey && input.key) {
|
if (input.appKey && input.key) {
|
||||||
|
@@ -1,14 +1,21 @@
|
|||||||
import User from '../../models/user';
|
import User from '../../models/user';
|
||||||
|
import Role from '../../models/role';
|
||||||
|
import Context from '../../types/express/context';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
input: {
|
input: {
|
||||||
fullName: string;
|
fullName: string;
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
role: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const createUser = async (_parent: unknown, params: Params) => {
|
const createUser = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
|
context.currentUser.can('create', 'User');
|
||||||
|
|
||||||
const { fullName, email, password } = params.input;
|
const { fullName, email, password } = params.input;
|
||||||
|
|
||||||
const existingUser = await User.query().findOne({ email });
|
const existingUser = await User.query().findOne({ email });
|
||||||
@@ -17,12 +24,23 @@ const createUser = async (_parent: unknown, params: Params) => {
|
|||||||
throw new Error('User already exists!');
|
throw new Error('User already exists!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await User.query().insert({
|
const userPayload: Partial<User> = {
|
||||||
fullName,
|
fullName,
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
role: 'user',
|
};
|
||||||
});
|
|
||||||
|
try {
|
||||||
|
context.currentUser.can('update', 'Role');
|
||||||
|
|
||||||
|
userPayload.roleId = params.input.role.id;
|
||||||
|
} catch {
|
||||||
|
// void
|
||||||
|
const role = await Role.query().findOne({ key: 'user' });
|
||||||
|
userPayload.roleId = role.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.query().insert(userPayload);
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
};
|
};
|
||||||
|
@@ -11,6 +11,8 @@ const deleteConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('delete', 'Connection');
|
||||||
|
|
||||||
await context.currentUser
|
await context.currentUser
|
||||||
.$relatedQuery('connections')
|
.$relatedQuery('connections')
|
||||||
.delete()
|
.delete()
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
import { Duration } from 'luxon';
|
||||||
|
import Context from '../../types/express/context';
|
||||||
|
import deleteUserQueue from '../../queues/delete-user.ee';
|
||||||
|
|
||||||
|
const deleteCurrentUser = async (_parent: unknown, params: never, context: Context) => {
|
||||||
|
const id = context.currentUser.id;
|
||||||
|
|
||||||
|
await context.currentUser.$query().delete();
|
||||||
|
|
||||||
|
const jobName = `Delete user - ${id}`;
|
||||||
|
const jobPayload = { id };
|
||||||
|
const millisecondsFor30Days = Duration.fromObject({ days: 30 }).toMillis();
|
||||||
|
const jobOptions = {
|
||||||
|
delay: millisecondsFor30Days
|
||||||
|
};
|
||||||
|
|
||||||
|
await deleteUserQueue.add(jobName, jobPayload, jobOptions);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default deleteCurrentUser;
|
@@ -13,6 +13,8 @@ const deleteFlow = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('delete', 'Flow');
|
||||||
|
|
||||||
const flow = await context.currentUser
|
const flow = await context.currentUser
|
||||||
.$relatedQuery('flows')
|
.$relatedQuery('flows')
|
||||||
.findOne({
|
.findOne({
|
||||||
|
47
packages/backend/src/graphql/mutations/delete-role.ee.ts
Normal file
47
packages/backend/src/graphql/mutations/delete-role.ee.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import Role from '../../models/role';
|
||||||
|
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
|
||||||
|
import Context from '../../types/express/context';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
input: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteRole = async (
|
||||||
|
_parent: unknown,
|
||||||
|
params: Params,
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
|
context.currentUser.can('delete', 'Role');
|
||||||
|
|
||||||
|
const role = await Role.query().findById(params.input.id).throwIfNotFound();
|
||||||
|
const count = await role.$relatedQuery('users').resultSize();
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
throw new Error('All users must be migrated away from the role!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role.isAdmin) {
|
||||||
|
throw new Error('Admin role cannot be deleted!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const samlAuthProviderUsingDefaultRole = await SamlAuthProvider.query()
|
||||||
|
.where({ default_role_id: role.id })
|
||||||
|
.limit(1)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (samlAuthProviderUsingDefaultRole) {
|
||||||
|
throw new Error(
|
||||||
|
'You need to change the default role in the SAML configuration before deleting this role.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete permissions first
|
||||||
|
await role.$relatedQuery('permissions').delete();
|
||||||
|
await role.$query().delete();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default deleteRole;
|
@@ -11,6 +11,8 @@ const deleteStep = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
const step = await context.currentUser
|
const step = await context.currentUser
|
||||||
.$relatedQuery('steps')
|
.$relatedQuery('steps')
|
||||||
.withGraphFetched('flow')
|
.withGraphFetched('flow')
|
||||||
|
@@ -1,11 +1,24 @@
|
|||||||
import Context from '../../types/express/context';
|
|
||||||
import deleteUserQueue from '../../queues/delete-user.ee';
|
|
||||||
import { Duration } from 'luxon';
|
import { Duration } from 'luxon';
|
||||||
|
import Context from '../../types/express/context';
|
||||||
|
import User from '../../models/user';
|
||||||
|
import deleteUserQueue from '../../queues/delete-user.ee';
|
||||||
|
|
||||||
const deleteUser = async (_parent: unknown, params: never, context: Context) => {
|
type Params = {
|
||||||
const id = context.currentUser.id;
|
input: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
await context.currentUser.$query().delete();
|
const deleteUser = async (
|
||||||
|
_parent: unknown,
|
||||||
|
params: Params,
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
|
context.currentUser.can('delete', 'User');
|
||||||
|
|
||||||
|
const id = params.input.id;
|
||||||
|
|
||||||
|
await User.query().deleteById(id);
|
||||||
|
|
||||||
const jobName = `Delete user - ${id}`;
|
const jobName = `Delete user - ${id}`;
|
||||||
const jobPayload = { id };
|
const jobPayload = { id };
|
||||||
|
@@ -53,6 +53,8 @@ const duplicateFlow = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Flow');
|
||||||
|
|
||||||
const flow = await context.currentUser
|
const flow = await context.currentUser
|
||||||
.$relatedQuery('flows')
|
.$relatedQuery('flows')
|
||||||
.withGraphJoined('[steps]')
|
.withGraphJoined('[steps]')
|
||||||
|
@@ -12,6 +12,8 @@ const executeFlow = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
const { stepId } = params.input;
|
const { stepId } = params.input;
|
||||||
|
|
||||||
const untilStep = await context.currentUser
|
const untilStep = await context.currentUser
|
||||||
|
@@ -13,6 +13,8 @@ const generateAuthUrl = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
const connection = await context.currentUser
|
const connection = await context.currentUser
|
||||||
.$relatedQuery('connections')
|
.$relatedQuery('connections')
|
||||||
.findOne({
|
.findOne({
|
||||||
|
@@ -13,7 +13,7 @@ const TOKEN_EXPIRES_IN = '14d';
|
|||||||
|
|
||||||
const login = async (_parent: unknown, params: Params) => {
|
const login = async (_parent: unknown, params: Params) => {
|
||||||
const user = await User.query().findOne({
|
const user = await User.query().findOne({
|
||||||
email: params.input.email,
|
email: params.input.email.toLowerCase(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user && (await user.login(params.input.password))) {
|
if (user && (await user.login(params.input.password))) {
|
||||||
|
33
packages/backend/src/graphql/mutations/register-user.ee.ts
Normal file
33
packages/backend/src/graphql/mutations/register-user.ee.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import User from '../../models/user';
|
||||||
|
import Role from '../../models/role';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
input: {
|
||||||
|
fullName: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerUser = async (_parent: unknown, params: Params) => {
|
||||||
|
const { fullName, email, password } = params.input;
|
||||||
|
|
||||||
|
const existingUser = await User.query().findOne({ email });
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
throw new Error('User already exists!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = await Role.query().findOne({ key: 'user' });
|
||||||
|
|
||||||
|
const user = await User.query().insert({
|
||||||
|
fullName,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
roleId: role.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default registerUser;
|
@@ -11,6 +11,8 @@ const resetConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
let connection = await context.currentUser
|
let connection = await context.currentUser
|
||||||
.$relatedQuery('connections')
|
.$relatedQuery('connections')
|
||||||
.findOne({
|
.findOne({
|
||||||
|
@@ -13,6 +13,8 @@ const updateConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
let connection = await context.currentUser
|
let connection = await context.currentUser
|
||||||
.$relatedQuery('connections')
|
.$relatedQuery('connections')
|
||||||
.findOne({
|
.findOne({
|
||||||
|
@@ -8,7 +8,7 @@ type Params = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateUser = async (
|
const updateCurrentUser = async (
|
||||||
_parent: unknown,
|
_parent: unknown,
|
||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
@@ -22,4 +22,4 @@ const updateUser = async (
|
|||||||
return user;
|
return user;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default updateUser;
|
export default updateCurrentUser;
|
@@ -18,6 +18,8 @@ const updateFlowStatus = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('publish', 'Flow');
|
||||||
|
|
||||||
let flow = await context.currentUser
|
let flow = await context.currentUser
|
||||||
.$relatedQuery('flows')
|
.$relatedQuery('flows')
|
||||||
.findOne({
|
.findOne({
|
||||||
@@ -55,7 +57,7 @@ const updateFlowStatus = async (
|
|||||||
} else {
|
} else {
|
||||||
if (newActiveValue) {
|
if (newActiveValue) {
|
||||||
flow = await flow.$query().patchAndFetch({
|
flow = await flow.$query().patchAndFetch({
|
||||||
published_at: new Date().toISOString(),
|
publishedAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const jobName = `${JOB_NAME}-${flow.id}`;
|
const jobName = `${JOB_NAME}-${flow.id}`;
|
||||||
@@ -78,9 +80,12 @@ const updateFlowStatus = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flow = await flow.$query().withGraphFetched('steps').patchAndFetch({
|
flow = await flow
|
||||||
active: newActiveValue,
|
.$query()
|
||||||
});
|
.withGraphFetched('steps')
|
||||||
|
.patchAndFetch({
|
||||||
|
active: newActiveValue,
|
||||||
|
});
|
||||||
|
|
||||||
return flow;
|
return flow;
|
||||||
};
|
};
|
||||||
|
@@ -12,6 +12,8 @@ const updateFlow = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
let flow = await context.currentUser
|
let flow = await context.currentUser
|
||||||
.$relatedQuery('flows')
|
.$relatedQuery('flows')
|
||||||
.findOne({
|
.findOne({
|
||||||
|
91
packages/backend/src/graphql/mutations/update-role.ee.ts
Normal file
91
packages/backend/src/graphql/mutations/update-role.ee.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import Context from '../../types/express/context';
|
||||||
|
import Role from '../../models/role';
|
||||||
|
import Permission from '../../models/permission';
|
||||||
|
import permissionCatalog from '../../helpers/permission-catalog.ee';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
input: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
permissions: Permission[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateRole = async (
|
||||||
|
_parent: unknown,
|
||||||
|
params: Params,
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
|
context.currentUser.can('update', 'Role');
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
permissions,
|
||||||
|
} = params.input;
|
||||||
|
|
||||||
|
const role = await Role
|
||||||
|
.query()
|
||||||
|
.findById(id)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedRole = await Role.transaction(async (trx) => {
|
||||||
|
await role.$relatedQuery('permissions', trx).delete();
|
||||||
|
|
||||||
|
if (permissions?.length) {
|
||||||
|
const sanitizedPermissions = permissions
|
||||||
|
.filter((permission) => {
|
||||||
|
const {
|
||||||
|
action,
|
||||||
|
subject,
|
||||||
|
conditions,
|
||||||
|
} = permission;
|
||||||
|
|
||||||
|
const relevantAction = permissionCatalog.actions.find(actionCatalogItem => actionCatalogItem.key === action);
|
||||||
|
const validSubject = relevantAction.subjects.includes(subject);
|
||||||
|
const validConditions = conditions.every(condition => {
|
||||||
|
return !!permissionCatalog
|
||||||
|
.conditions
|
||||||
|
.find((conditionCatalogItem) => conditionCatalogItem.key === condition);
|
||||||
|
})
|
||||||
|
|
||||||
|
return validSubject && validConditions;
|
||||||
|
})
|
||||||
|
.map((permission) => ({
|
||||||
|
...permission,
|
||||||
|
roleId: role.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await Permission.query().insert(sanitizedPermissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
await role
|
||||||
|
.$query(trx)
|
||||||
|
.patch(
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return await Role
|
||||||
|
.query(trx)
|
||||||
|
.leftJoinRelated({
|
||||||
|
permissions: true
|
||||||
|
})
|
||||||
|
.withGraphFetched({
|
||||||
|
permissions: true
|
||||||
|
})
|
||||||
|
.findById(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedRole;
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error('The role could not be updated!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateRole;
|
@@ -0,0 +1,45 @@
|
|||||||
|
import type { SamlConfig } from '@node-saml/passport-saml';
|
||||||
|
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
|
||||||
|
import Context from '../../types/express/context';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
input: {
|
||||||
|
name: string;
|
||||||
|
certificate: string;
|
||||||
|
signatureAlgorithm: SamlConfig['signatureAlgorithm'];
|
||||||
|
issuer: string;
|
||||||
|
entryPoint: string;
|
||||||
|
firstnameAttributeName: string;
|
||||||
|
surnameAttributeName: string;
|
||||||
|
emailAttributeName: string;
|
||||||
|
roleAttributeName: string;
|
||||||
|
defaultRoleId: string;
|
||||||
|
active: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSamlAuthProvider = async (
|
||||||
|
_parent: unknown,
|
||||||
|
params: Params,
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
|
context.currentUser.can('update', 'SamlAuthProvider');
|
||||||
|
|
||||||
|
const samlAuthProviderPayload: Partial<SamlAuthProvider> = {
|
||||||
|
...params.input,
|
||||||
|
};
|
||||||
|
|
||||||
|
const existingSamlAuthProvider = await SamlAuthProvider.query()
|
||||||
|
.limit(1)
|
||||||
|
.first()
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const samlAuthProvider = await SamlAuthProvider.query().patchAndFetchById(
|
||||||
|
existingSamlAuthProvider.id,
|
||||||
|
samlAuthProviderPayload
|
||||||
|
);
|
||||||
|
|
||||||
|
return samlAuthProvider;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateSamlAuthProvider;
|
@@ -23,6 +23,8 @@ const updateStep = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
const { input } = params;
|
const { input } = params;
|
||||||
|
|
||||||
let step = await context.currentUser
|
let step = await context.currentUser
|
||||||
|
43
packages/backend/src/graphql/mutations/update-user.ee.ts
Normal file
43
packages/backend/src/graphql/mutations/update-user.ee.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import Context from '../../types/express/context';
|
||||||
|
import User from '../../models/user';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
input: {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
fullName: string;
|
||||||
|
role: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUser = async (
|
||||||
|
_parent: unknown,
|
||||||
|
params: Params,
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
|
context.currentUser.can('update', 'User');
|
||||||
|
|
||||||
|
const userPayload: Partial<User> = {
|
||||||
|
email: params.input.email,
|
||||||
|
fullName: params.input.fullName,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
context.currentUser.can('update', 'Role');
|
||||||
|
|
||||||
|
userPayload.roleId = params.input.role.id;
|
||||||
|
} catch {
|
||||||
|
// void
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.query().patchAndFetchById(
|
||||||
|
params.input.id,
|
||||||
|
userPayload
|
||||||
|
);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateUser;
|
@@ -13,6 +13,8 @@ const verifyConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
let connection = await context.currentUser
|
let connection = await context.currentUser
|
||||||
.$relatedQuery('connections')
|
.$relatedQuery('connections')
|
||||||
.findOne({
|
.findOne({
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import App from '../../models/app';
|
import App from '../../models/app';
|
||||||
|
import Connection from '../../models/connection';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
@@ -6,11 +7,17 @@ type Params = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getApp = async (_parent: unknown, params: Params, context: Context) => {
|
const getApp = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
|
const conditions = context.currentUser.can('read', 'Connection');
|
||||||
|
|
||||||
|
const userConnections = context.currentUser.$relatedQuery('connections');
|
||||||
|
const allConnections = Connection.query();
|
||||||
|
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
||||||
|
|
||||||
const app = await App.findOneByKey(params.key);
|
const app = await App.findOneByKey(params.key);
|
||||||
|
|
||||||
if (context.currentUser) {
|
if (context.currentUser) {
|
||||||
const connections = await context.currentUser
|
const connections = await connectionBaseQuery
|
||||||
.$relatedQuery('connections')
|
.clone()
|
||||||
.select('connections.*')
|
.select('connections.*')
|
||||||
.fullOuterJoinRelated('steps')
|
.fullOuterJoinRelated('steps')
|
||||||
.where({
|
.where({
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import App from '../../models/app';
|
|
||||||
import { IApp } from '@automatisch/types';
|
import { IApp } from '@automatisch/types';
|
||||||
|
import App from '../../models/app';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
name: string;
|
name: string;
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
|
import { IConnection } from '@automatisch/types';
|
||||||
import App from '../../models/app';
|
import App from '../../models/app';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import { IApp, IConnection } from '@automatisch/types';
|
import Flow from '../../models/flow';
|
||||||
|
import Connection from '../../models/connection';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -11,17 +13,27 @@ const getConnectedApps = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
const conditions = context.currentUser.can('read', 'Connection');
|
||||||
|
|
||||||
|
const userConnections = context.currentUser.$relatedQuery('connections');
|
||||||
|
const allConnections = Connection.query();
|
||||||
|
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
||||||
|
|
||||||
|
const userFlows = context.currentUser.$relatedQuery('flows');
|
||||||
|
const allFlows = Flow.query();
|
||||||
|
const flowBaseQuery = conditions.isCreator ? userFlows : allFlows;
|
||||||
|
|
||||||
let apps = await App.findAll(params.name);
|
let apps = await App.findAll(params.name);
|
||||||
|
|
||||||
const connections = await context.currentUser
|
const connections = await connectionBaseQuery
|
||||||
.$relatedQuery('connections')
|
.clone()
|
||||||
.select('connections.key')
|
.select('connections.key')
|
||||||
.where({ draft: false })
|
.where({ draft: false })
|
||||||
.count('connections.id as count')
|
.count('connections.id as count')
|
||||||
.groupBy('connections.key');
|
.groupBy('connections.key');
|
||||||
|
|
||||||
const flows = await context.currentUser
|
const flows = await flowBaseQuery
|
||||||
.$relatedQuery('flows')
|
.clone()
|
||||||
.withGraphJoined('steps')
|
.withGraphJoined('steps')
|
||||||
.orderBy('created_at', 'desc');
|
.orderBy('created_at', 'desc');
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { IDynamicData, IJSONObject } from '@automatisch/types';
|
import { IDynamicData, IJSONObject } from '@automatisch/types';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import App from '../../models/app';
|
import App from '../../models/app';
|
||||||
|
import Step from '../../models/step';
|
||||||
import ExecutionStep from '../../models/execution-step';
|
import ExecutionStep from '../../models/execution-step';
|
||||||
import globalVariable from '../../helpers/global-variable';
|
import globalVariable from '../../helpers/global-variable';
|
||||||
import computeParameters from '../../helpers/compute-parameters';
|
import computeParameters from '../../helpers/compute-parameters';
|
||||||
@@ -16,8 +17,13 @@ const getDynamicData = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const step = await context.currentUser
|
const conditions = context.currentUser.can('update', 'Flow');
|
||||||
.$relatedQuery('steps')
|
const userSteps = context.currentUser.$relatedQuery('steps');
|
||||||
|
const allSteps = Step.query();
|
||||||
|
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
|
||||||
|
|
||||||
|
const step = await stepBaseQuery
|
||||||
|
.clone()
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
connection: true,
|
connection: true,
|
||||||
flow: true,
|
flow: true,
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { IDynamicFields, IJSONObject } from '@automatisch/types';
|
import { IDynamicFields, IJSONObject } from '@automatisch/types';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import App from '../../models/app';
|
import App from '../../models/app';
|
||||||
|
import Step from '../../models/step';
|
||||||
import globalVariable from '../../helpers/global-variable';
|
import globalVariable from '../../helpers/global-variable';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
@@ -14,8 +15,13 @@ const getDynamicFields = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const step = await context.currentUser
|
const conditions = context.currentUser.can('update', 'Flow');
|
||||||
.$relatedQuery('steps')
|
const userSteps = context.currentUser.$relatedQuery('steps');
|
||||||
|
const allSteps = Step.query();
|
||||||
|
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
|
||||||
|
|
||||||
|
const step = await stepBaseQuery
|
||||||
|
.clone()
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
connection: true,
|
connection: true,
|
||||||
flow: true,
|
flow: true,
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import paginate from '../../helpers/pagination';
|
import paginate from '../../helpers/pagination';
|
||||||
|
import Execution from '../../models/execution';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
executionId: string;
|
executionId: string;
|
||||||
@@ -12,8 +13,13 @@ const getExecutionSteps = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const execution = await context.currentUser
|
const conditions = context.currentUser.can('read', 'Execution');
|
||||||
.$relatedQuery('executions')
|
const userExecutions = context.currentUser.$relatedQuery('executions');
|
||||||
|
const allExecutions = Execution.query();
|
||||||
|
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
|
||||||
|
|
||||||
|
const execution = await executionBaseQuery
|
||||||
|
.clone()
|
||||||
.withSoftDeleted()
|
.withSoftDeleted()
|
||||||
.findById(params.executionId)
|
.findById(params.executionId)
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
|
import Execution from '../../models/execution';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
executionId: string;
|
executionId: string;
|
||||||
@@ -9,8 +10,13 @@ const getExecution = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const execution = await context.currentUser
|
const conditions = context.currentUser.can('read', 'Execution');
|
||||||
.$relatedQuery('executions')
|
const userExecutions = context.currentUser.$relatedQuery('executions');
|
||||||
|
const allExecutions = Execution.query();
|
||||||
|
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
|
||||||
|
|
||||||
|
const execution = await executionBaseQuery
|
||||||
|
.clone()
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
flow: {
|
flow: {
|
||||||
steps: true,
|
steps: true,
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { raw } from 'objection';
|
import { raw } from 'objection';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
|
import Execution from '../../models/execution';
|
||||||
import paginate from '../../helpers/pagination';
|
import paginate from '../../helpers/pagination';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
@@ -12,6 +13,12 @@ const getExecutions = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
const conditions = context.currentUser.can('read', 'Execution');
|
||||||
|
|
||||||
|
const userExecutions = context.currentUser.$relatedQuery('executions');
|
||||||
|
const allExecutions = Execution.query();
|
||||||
|
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
|
||||||
|
|
||||||
const selectStatusStatement = `
|
const selectStatusStatement = `
|
||||||
case
|
case
|
||||||
when count(*) filter (where execution_steps.status = 'failure') > 0
|
when count(*) filter (where execution_steps.status = 'failure') > 0
|
||||||
@@ -21,8 +28,8 @@ const getExecutions = async (
|
|||||||
as status
|
as status
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const executions = context.currentUser
|
const executions = executionBaseQuery
|
||||||
.$relatedQuery('executions')
|
.clone()
|
||||||
.joinRelated('executionSteps as execution_steps')
|
.joinRelated('executionSteps as execution_steps')
|
||||||
.select('executions.*', raw(selectStatusStatement))
|
.select('executions.*', raw(selectStatusStatement))
|
||||||
.withSoftDeleted()
|
.withSoftDeleted()
|
||||||
|
@@ -1,12 +1,18 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
|
import Flow from '../../models/flow';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFlow = async (_parent: unknown, params: Params, context: Context) => {
|
const getFlow = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
const flow = await context.currentUser
|
const conditions = context.currentUser.can('read', 'Flow');
|
||||||
.$relatedQuery('flows')
|
const userFlows = context.currentUser.$relatedQuery('flows');
|
||||||
|
const allFlows = Flow.query();
|
||||||
|
const baseQuery = conditions.isCreator ? userFlows : allFlows;
|
||||||
|
|
||||||
|
const flow = await baseQuery
|
||||||
|
.clone()
|
||||||
.withGraphJoined('[steps.[connection]]')
|
.withGraphJoined('[steps.[connection]]')
|
||||||
.orderBy('steps.position', 'asc')
|
.orderBy('steps.position', 'asc')
|
||||||
.findOne({ 'flows.id': params.id })
|
.findOne({ 'flows.id': params.id })
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import Flow from '../../models/flow';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import paginate from '../../helpers/pagination';
|
import paginate from '../../helpers/pagination';
|
||||||
|
|
||||||
@@ -10,8 +11,13 @@ type Params = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getFlows = async (_parent: unknown, params: Params, context: Context) => {
|
const getFlows = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
const flowsQuery = context.currentUser
|
const conditions = context.currentUser.can('read', 'Flow');
|
||||||
.$relatedQuery('flows')
|
const userFlows = context.currentUser.$relatedQuery('flows');
|
||||||
|
const allFlows = Flow.query();
|
||||||
|
const baseQuery = conditions.isCreator ? userFlows : allFlows;
|
||||||
|
|
||||||
|
const flowsQuery = baseQuery
|
||||||
|
.clone()
|
||||||
.joinRelated({
|
.joinRelated({
|
||||||
steps: true,
|
steps: true,
|
||||||
})
|
})
|
||||||
|
@@ -0,0 +1,7 @@
|
|||||||
|
import permissionCatalog from '../../helpers/permission-catalog.ee';
|
||||||
|
|
||||||
|
const getPermissionCatalog = async () => {
|
||||||
|
return permissionCatalog;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getPermissionCatalog;
|
23
packages/backend/src/graphql/queries/get-role.ee.ts
Normal file
23
packages/backend/src/graphql/queries/get-role.ee.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import Context from '../../types/express/context';
|
||||||
|
import Role from '../../models/role';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRole = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
|
context.currentUser.can('read', 'Role');
|
||||||
|
|
||||||
|
return await Role
|
||||||
|
.query()
|
||||||
|
.leftJoinRelated({
|
||||||
|
permissions: true
|
||||||
|
})
|
||||||
|
.withGraphFetched({
|
||||||
|
permissions: true
|
||||||
|
})
|
||||||
|
.findById(params.id)
|
||||||
|
.throwIfNotFound();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getRole;
|
10
packages/backend/src/graphql/queries/get-roles.ee.ts
Normal file
10
packages/backend/src/graphql/queries/get-roles.ee.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import Context from '../../types/express/context';
|
||||||
|
import Role from '../../models/role';
|
||||||
|
|
||||||
|
const getRoles = async (_parent: unknown, params: unknown, context: Context) => {
|
||||||
|
context.currentUser.can('read', 'Role');
|
||||||
|
|
||||||
|
return await Role.query().orderBy('name');
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getRoles;
|
@@ -0,0 +1,9 @@
|
|||||||
|
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
|
||||||
|
|
||||||
|
const getSamlAuthProviders = async () => {
|
||||||
|
const providers = await SamlAuthProvider.query().where({ active: true });
|
||||||
|
|
||||||
|
return providers;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getSamlAuthProviders;
|
@@ -1,6 +1,7 @@
|
|||||||
import Context from '../../types/express/context';
|
|
||||||
import ExecutionStep from '../../models/execution-step';
|
|
||||||
import { ref } from 'objection';
|
import { ref } from 'objection';
|
||||||
|
import ExecutionStep from '../../models/execution-step';
|
||||||
|
import Step from '../../models/step';
|
||||||
|
import Context from '../../types/express/context';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
stepId: string;
|
stepId: string;
|
||||||
@@ -11,13 +12,18 @@ const getStepWithTestExecutions = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const step = await context.currentUser
|
const conditions = context.currentUser.can('update', 'Flow');
|
||||||
.$relatedQuery('steps')
|
const userSteps = context.currentUser.$relatedQuery('steps');
|
||||||
|
const allSteps = Step.query();
|
||||||
|
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
|
||||||
|
|
||||||
|
const step = await stepBaseQuery
|
||||||
|
.clone()
|
||||||
.findOne({ 'steps.id': params.stepId })
|
.findOne({ 'steps.id': params.stepId })
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
const previousStepsWithCurrentStep = await context.currentUser
|
const previousStepsWithCurrentStep = await stepBaseQuery
|
||||||
.$relatedQuery('steps')
|
.clone()
|
||||||
.withGraphJoined('executionSteps')
|
.withGraphJoined('executionSteps')
|
||||||
.where('flow_id', '=', step.flowId)
|
.where('flow_id', '=', step.flowId)
|
||||||
.andWhere('position', '<', step.position)
|
.andWhere('position', '<', step.position)
|
||||||
|
23
packages/backend/src/graphql/queries/get-user.ts
Normal file
23
packages/backend/src/graphql/queries/get-user.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import Context from '../../types/express/context';
|
||||||
|
import User from '../../models/user';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUser = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
|
context.currentUser.can('read', 'User');
|
||||||
|
|
||||||
|
return await User
|
||||||
|
.query()
|
||||||
|
.leftJoinRelated({
|
||||||
|
role: true
|
||||||
|
})
|
||||||
|
.withGraphFetched({
|
||||||
|
role: true
|
||||||
|
})
|
||||||
|
.findById(params.id)
|
||||||
|
.throwIfNotFound();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getUser;
|
26
packages/backend/src/graphql/queries/get-users.ts
Normal file
26
packages/backend/src/graphql/queries/get-users.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import Context from '../../types/express/context';
|
||||||
|
import paginate from '../../helpers/pagination';
|
||||||
|
import User from '../../models/user';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUsers = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
|
context.currentUser.can('read', 'User');
|
||||||
|
|
||||||
|
const usersQuery = User
|
||||||
|
.query()
|
||||||
|
.leftJoinRelated({
|
||||||
|
role: true
|
||||||
|
})
|
||||||
|
.withGraphFetched({
|
||||||
|
role: true
|
||||||
|
})
|
||||||
|
.orderBy('full_name', 'desc');
|
||||||
|
|
||||||
|
return paginate(usersQuery, params.limit, params.offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getUsers;
|
@@ -1,5 +1,6 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import App from '../../models/app';
|
import App from '../../models/app';
|
||||||
|
import Connection from '../../models/connection';
|
||||||
import globalVariable from '../../helpers/global-variable';
|
import globalVariable from '../../helpers/global-variable';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
@@ -12,8 +13,13 @@ const testConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
let connection = await context.currentUser
|
const conditions = context.currentUser.can('update', 'Connection');
|
||||||
.$relatedQuery('connections')
|
const userConnections = context.currentUser.$relatedQuery('connections');
|
||||||
|
const allConnections = Connection.query();
|
||||||
|
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
||||||
|
|
||||||
|
let connection = await connectionBaseQuery
|
||||||
|
.clone()
|
||||||
.findOne({
|
.findOne({
|
||||||
id: params.id,
|
id: params.id,
|
||||||
})
|
})
|
||||||
|
@@ -1,47 +1,59 @@
|
|||||||
import getApps from './queries/get-apps';
|
|
||||||
import getApp from './queries/get-app';
|
import getApp from './queries/get-app';
|
||||||
|
import getApps from './queries/get-apps';
|
||||||
|
import getAutomatischInfo from './queries/get-automatisch-info';
|
||||||
|
import getBillingAndUsage from './queries/get-billing-and-usage.ee';
|
||||||
import getConnectedApps from './queries/get-connected-apps';
|
import getConnectedApps from './queries/get-connected-apps';
|
||||||
import testConnection from './queries/test-connection';
|
import getCurrentUser from './queries/get-current-user';
|
||||||
import getFlow from './queries/get-flow';
|
|
||||||
import getFlows from './queries/get-flows';
|
|
||||||
import getStepWithTestExecutions from './queries/get-step-with-test-executions';
|
|
||||||
import getExecution from './queries/get-execution';
|
|
||||||
import getExecutions from './queries/get-executions';
|
|
||||||
import getExecutionSteps from './queries/get-execution-steps';
|
|
||||||
import getDynamicData from './queries/get-dynamic-data';
|
import getDynamicData from './queries/get-dynamic-data';
|
||||||
import getDynamicFields from './queries/get-dynamic-fields';
|
import getDynamicFields from './queries/get-dynamic-fields';
|
||||||
import getCurrentUser from './queries/get-current-user';
|
import getExecution from './queries/get-execution';
|
||||||
import getPaymentPlans from './queries/get-payment-plans.ee';
|
import getExecutionSteps from './queries/get-execution-steps';
|
||||||
import getPaddleInfo from './queries/get-paddle-info.ee';
|
import getExecutions from './queries/get-executions';
|
||||||
import getBillingAndUsage from './queries/get-billing-and-usage.ee';
|
import getFlow from './queries/get-flow';
|
||||||
|
import getFlows from './queries/get-flows';
|
||||||
|
import getUser from './queries/get-user';
|
||||||
|
import getUsers from './queries/get-users';
|
||||||
import getInvoices from './queries/get-invoices.ee';
|
import getInvoices from './queries/get-invoices.ee';
|
||||||
import getAutomatischInfo from './queries/get-automatisch-info';
|
import getPaddleInfo from './queries/get-paddle-info.ee';
|
||||||
import getTrialStatus from './queries/get-trial-status.ee';
|
import getPaymentPlans from './queries/get-payment-plans.ee';
|
||||||
|
import getPermissionCatalog from './queries/get-permission-catalog.ee';
|
||||||
|
import getRole from './queries/get-role.ee';
|
||||||
|
import getRoles from './queries/get-roles.ee';
|
||||||
|
import getSamlAuthProviders from './queries/get-saml-auth-providers.ee';
|
||||||
|
import getStepWithTestExecutions from './queries/get-step-with-test-executions';
|
||||||
import getSubscriptionStatus from './queries/get-subscription-status.ee';
|
import getSubscriptionStatus from './queries/get-subscription-status.ee';
|
||||||
|
import getTrialStatus from './queries/get-trial-status.ee';
|
||||||
import healthcheck from './queries/healthcheck';
|
import healthcheck from './queries/healthcheck';
|
||||||
|
import testConnection from './queries/test-connection';
|
||||||
|
|
||||||
const queryResolvers = {
|
const queryResolvers = {
|
||||||
getApps,
|
|
||||||
getApp,
|
getApp,
|
||||||
|
getApps,
|
||||||
|
getAutomatischInfo,
|
||||||
|
getBillingAndUsage,
|
||||||
getConnectedApps,
|
getConnectedApps,
|
||||||
testConnection,
|
getCurrentUser,
|
||||||
getFlow,
|
getDynamicData,
|
||||||
getFlows,
|
getDynamicFields,
|
||||||
getStepWithTestExecutions,
|
|
||||||
getExecution,
|
getExecution,
|
||||||
getExecutions,
|
getExecutions,
|
||||||
getExecutionSteps,
|
getExecutionSteps,
|
||||||
getDynamicData,
|
getFlow,
|
||||||
getDynamicFields,
|
getFlows,
|
||||||
getCurrentUser,
|
|
||||||
getPaymentPlans,
|
|
||||||
getPaddleInfo,
|
|
||||||
getBillingAndUsage,
|
|
||||||
getInvoices,
|
getInvoices,
|
||||||
getAutomatischInfo,
|
getPaddleInfo,
|
||||||
getTrialStatus,
|
getPaymentPlans,
|
||||||
|
getPermissionCatalog,
|
||||||
|
getRole,
|
||||||
|
getRoles,
|
||||||
|
getSamlAuthProviders,
|
||||||
|
getStepWithTestExecutions,
|
||||||
getSubscriptionStatus,
|
getSubscriptionStatus,
|
||||||
|
getTrialStatus,
|
||||||
|
getUser,
|
||||||
|
getUsers,
|
||||||
healthcheck,
|
healthcheck,
|
||||||
|
testConnection,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default queryResolvers;
|
export default queryResolvers;
|
||||||
|
@@ -41,31 +41,45 @@ type Query {
|
|||||||
getAutomatischInfo: GetAutomatischInfo
|
getAutomatischInfo: GetAutomatischInfo
|
||||||
getTrialStatus: GetTrialStatus
|
getTrialStatus: GetTrialStatus
|
||||||
getSubscriptionStatus: GetSubscriptionStatus
|
getSubscriptionStatus: GetSubscriptionStatus
|
||||||
|
getSamlAuthProviders: [GetSamlAuthProviders]
|
||||||
|
getUsers(limit: Int!, offset: Int!): UserConnection
|
||||||
|
getUser(id: String!): User
|
||||||
|
getRoles: [Role]
|
||||||
|
getRole(id: String!): Role
|
||||||
|
getPermissionCatalog: PermissionCatalog
|
||||||
healthcheck: AppHealth
|
healthcheck: AppHealth
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
createConnection(input: CreateConnectionInput): Connection
|
createConnection(input: CreateConnectionInput): Connection
|
||||||
generateAuthUrl(input: GenerateAuthUrlInput): AuthLink
|
|
||||||
updateConnection(input: UpdateConnectionInput): Connection
|
|
||||||
resetConnection(input: ResetConnectionInput): Connection
|
|
||||||
verifyConnection(input: VerifyConnectionInput): Connection
|
|
||||||
deleteConnection(input: DeleteConnectionInput): Boolean
|
|
||||||
createFlow(input: CreateFlowInput): Flow
|
createFlow(input: CreateFlowInput): Flow
|
||||||
|
createRole(input: CreateRoleInput): Role
|
||||||
|
createStep(input: CreateStepInput): Step
|
||||||
|
createUser(input: CreateUserInput): User
|
||||||
|
deleteConnection(input: DeleteConnectionInput): Boolean
|
||||||
|
deleteCurrentUser: Boolean
|
||||||
|
deleteFlow(input: DeleteFlowInput): Boolean
|
||||||
|
deleteRole(input: DeleteRoleInput): Boolean
|
||||||
|
deleteStep(input: DeleteStepInput): Step
|
||||||
|
deleteUser(input: DeleteUserInput): Boolean
|
||||||
|
duplicateFlow(input: DuplicateFlowInput): Flow
|
||||||
|
executeFlow(input: ExecuteFlowInput): executeFlowType
|
||||||
|
forgotPassword(input: ForgotPasswordInput): Boolean
|
||||||
|
generateAuthUrl(input: GenerateAuthUrlInput): AuthLink
|
||||||
|
login(input: LoginInput): Auth
|
||||||
|
registerUser(input: RegisterUserInput): User
|
||||||
|
resetConnection(input: ResetConnectionInput): Connection
|
||||||
|
resetPassword(input: ResetPasswordInput): Boolean
|
||||||
|
updateConnection(input: UpdateConnectionInput): Connection
|
||||||
|
updateCurrentUser(input: UpdateCurrentUserInput): User
|
||||||
updateFlow(input: UpdateFlowInput): Flow
|
updateFlow(input: UpdateFlowInput): Flow
|
||||||
updateFlowStatus(input: UpdateFlowStatusInput): Flow
|
updateFlowStatus(input: UpdateFlowStatusInput): Flow
|
||||||
executeFlow(input: ExecuteFlowInput): executeFlowType
|
updateRole(input: UpdateRoleInput): Role
|
||||||
deleteFlow(input: DeleteFlowInput): Boolean
|
|
||||||
duplicateFlow(input: DuplicateFlowInput): Flow
|
|
||||||
createStep(input: CreateStepInput): Step
|
|
||||||
updateStep(input: UpdateStepInput): Step
|
updateStep(input: UpdateStepInput): Step
|
||||||
deleteStep(input: DeleteStepInput): Step
|
|
||||||
createUser(input: CreateUserInput): User
|
|
||||||
deleteUser: Boolean
|
|
||||||
updateUser(input: UpdateUserInput): User
|
updateUser(input: UpdateUserInput): User
|
||||||
forgotPassword(input: ForgotPasswordInput): Boolean
|
verifyConnection(input: VerifyConnectionInput): Connection
|
||||||
resetPassword(input: ResetPasswordInput): Boolean
|
createSamlAuthProvider(input: CreateSamlAuthProviderInput): SamlAuthProvider
|
||||||
login(input: LoginInput): Auth
|
updateSamlAuthProvider(input: UpdateSamlAuthProviderInput): SamlAuthProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -277,6 +291,29 @@ type Execution {
|
|||||||
flow: Flow
|
flow: Flow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SamlAuthProvider {
|
||||||
|
id: String
|
||||||
|
name: String
|
||||||
|
certificate: String
|
||||||
|
signatureAlgorithm: String
|
||||||
|
issuer: String
|
||||||
|
entryPoint: String
|
||||||
|
firstnameAttributeName: String
|
||||||
|
surnameAttributeName: String
|
||||||
|
emailAttributeName: String
|
||||||
|
roleAttributeName: String
|
||||||
|
active: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserConnection {
|
||||||
|
edges: [UserEdge]
|
||||||
|
pageInfo: PageInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserEdge {
|
||||||
|
node: User
|
||||||
|
}
|
||||||
|
|
||||||
input CreateConnectionInput {
|
input CreateConnectionInput {
|
||||||
key: String!
|
key: String!
|
||||||
formattedData: JSONObject!
|
formattedData: JSONObject!
|
||||||
@@ -299,6 +336,34 @@ input VerifyConnectionInput {
|
|||||||
id: String!
|
id: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input CreateSamlAuthProviderInput {
|
||||||
|
name: String!
|
||||||
|
certificate: String!
|
||||||
|
signatureAlgorithm: String!
|
||||||
|
issuer: String!
|
||||||
|
entryPoint: String!
|
||||||
|
firstnameAttributeName: String!
|
||||||
|
surnameAttributeName: String!
|
||||||
|
emailAttributeName: String!
|
||||||
|
roleAttributeName: String!
|
||||||
|
defaultRoleId: String!
|
||||||
|
active: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateSamlAuthProviderInput {
|
||||||
|
name: String!
|
||||||
|
certificate: String!
|
||||||
|
signatureAlgorithm: String!
|
||||||
|
issuer: String!
|
||||||
|
entryPoint: String!
|
||||||
|
firstnameAttributeName: String!
|
||||||
|
surnameAttributeName: String!
|
||||||
|
emailAttributeName: String!
|
||||||
|
roleAttributeName: String!
|
||||||
|
defaultRoleId: String!
|
||||||
|
active: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
input DeleteConnectionInput {
|
input DeleteConnectionInput {
|
||||||
id: String!
|
id: String!
|
||||||
}
|
}
|
||||||
@@ -360,9 +425,31 @@ input CreateUserInput {
|
|||||||
fullName: String!
|
fullName: String!
|
||||||
email: String!
|
email: String!
|
||||||
password: String!
|
password: String!
|
||||||
|
role: UserRoleInput!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UserRoleInput {
|
||||||
|
id: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input UpdateUserInput {
|
input UpdateUserInput {
|
||||||
|
id: String!
|
||||||
|
fullName: String
|
||||||
|
email: String
|
||||||
|
role: UserRoleInput
|
||||||
|
}
|
||||||
|
|
||||||
|
input DeleteUserInput {
|
||||||
|
id: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input RegisterUserInput {
|
||||||
|
fullName: String!
|
||||||
|
email: String!
|
||||||
|
password: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateCurrentUserInput {
|
||||||
email: String
|
email: String
|
||||||
password: String
|
password: String
|
||||||
fullName: String
|
fullName: String
|
||||||
@@ -382,6 +469,29 @@ input LoginInput {
|
|||||||
password: String!
|
password: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input PermissionInput {
|
||||||
|
action: String!
|
||||||
|
subject: String!
|
||||||
|
conditions: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateRoleInput {
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
permissions: [PermissionInput]
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateRoleInput {
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
permissions: [PermissionInput]
|
||||||
|
}
|
||||||
|
|
||||||
|
input DeleteRoleInput {
|
||||||
|
id: String!
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
|
The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
|
||||||
"""
|
"""
|
||||||
@@ -453,11 +563,21 @@ type User {
|
|||||||
id: String
|
id: String
|
||||||
fullName: String
|
fullName: String
|
||||||
email: String
|
email: String
|
||||||
role: String
|
role: Role
|
||||||
|
permissions: [Permission]
|
||||||
createdAt: String
|
createdAt: String
|
||||||
updatedAt: String
|
updatedAt: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Role {
|
||||||
|
id: String
|
||||||
|
name: String
|
||||||
|
key: String
|
||||||
|
description: String
|
||||||
|
isAdmin: Boolean
|
||||||
|
permissions: [Permission]
|
||||||
|
}
|
||||||
|
|
||||||
type PageInfo {
|
type PageInfo {
|
||||||
currentPage: Int!
|
currentPage: Int!
|
||||||
totalPages: Int!
|
totalPages: Int!
|
||||||
@@ -554,6 +674,41 @@ type PaymentPlan {
|
|||||||
productId: String
|
productId: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetSamlAuthProviders {
|
||||||
|
id: String
|
||||||
|
name: String
|
||||||
|
issuer: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Permission {
|
||||||
|
id: String
|
||||||
|
action: String
|
||||||
|
subject: String
|
||||||
|
conditions: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
type PermissionCatalog {
|
||||||
|
actions: [Action]
|
||||||
|
subjects: [Subject]
|
||||||
|
conditions: [Condition]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Action {
|
||||||
|
label: String
|
||||||
|
key: String
|
||||||
|
subjects: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Condition {
|
||||||
|
key: String
|
||||||
|
label: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subject {
|
||||||
|
label: String
|
||||||
|
key: String
|
||||||
|
}
|
||||||
|
|
||||||
schema {
|
schema {
|
||||||
query: Query
|
query: Query
|
||||||
mutation: Mutation
|
mutation: Mutation
|
||||||
|
@@ -12,7 +12,17 @@ const isAuthenticated = rule()(async (_parent, _args, req) => {
|
|||||||
const { userId } = jwt.verify(token, appConfig.appSecretKey) as {
|
const { userId } = jwt.verify(token, appConfig.appSecretKey) as {
|
||||||
userId: string;
|
userId: string;
|
||||||
};
|
};
|
||||||
req.currentUser = await User.query().findById(userId).throwIfNotFound();
|
req.currentUser = await User
|
||||||
|
.query()
|
||||||
|
.findById(userId)
|
||||||
|
.leftJoinRelated({
|
||||||
|
role: true,
|
||||||
|
permissions: true,
|
||||||
|
})
|
||||||
|
.withGraphFetched({
|
||||||
|
role: true,
|
||||||
|
permissions: true,
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -25,13 +35,14 @@ const authentication = shield(
|
|||||||
Query: {
|
Query: {
|
||||||
'*': isAuthenticated,
|
'*': isAuthenticated,
|
||||||
getAutomatischInfo: allow,
|
getAutomatischInfo: allow,
|
||||||
|
getSamlAuthProviders: allow,
|
||||||
healthcheck: allow,
|
healthcheck: allow,
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
'*': isAuthenticated,
|
'*': isAuthenticated,
|
||||||
login: allow,
|
registerUser: allow,
|
||||||
createUser: allow,
|
|
||||||
forgotPassword: allow,
|
forgotPassword: allow,
|
||||||
|
login: allow,
|
||||||
resetPassword: allow,
|
resetPassword: allow,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
24
packages/backend/src/helpers/axios-with-proxy.ts
Normal file
24
packages/backend/src/helpers/axios-with-proxy.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||||
|
import { HttpProxyAgent } from 'http-proxy-agent';
|
||||||
|
|
||||||
|
const config: AxiosRequestConfig = {};
|
||||||
|
const httpProxyUrl = process.env.http_proxy;
|
||||||
|
const httpsProxyUrl = process.env.https_proxy;
|
||||||
|
const supportsProxy = httpProxyUrl || httpsProxyUrl;
|
||||||
|
|
||||||
|
if (supportsProxy) {
|
||||||
|
if (httpProxyUrl) {
|
||||||
|
config.httpAgent = new HttpProxyAgent(process.env.http_proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (httpsProxyUrl) {
|
||||||
|
config.httpsAgent = new HttpsProxyAgent(process.env.https_proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.proxy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const axiosWithProxyInstance = axios.create(config);
|
||||||
|
|
||||||
|
export default axiosWithProxyInstance;
|
@@ -1,3 +1,4 @@
|
|||||||
|
// TODO: replace with axios-with-proxy when needed
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import appConfig from '../../config/app';
|
import appConfig from '../../config/app';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
// TODO: replace with axios-with-proxy
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
import memoryCache from 'memory-cache';
|
import memoryCache from 'memory-cache';
|
||||||
|
@@ -3,7 +3,7 @@ import ExecutionStep from '../models/execution-step';
|
|||||||
import get from 'lodash.get';
|
import get from 'lodash.get';
|
||||||
|
|
||||||
// INFO: don't remove space in allowed character group!
|
// INFO: don't remove space in allowed character group!
|
||||||
const variableRegExp = /({{step\.[\da-zA-Z-]+(?:\.[\da-zA-Z-_ ]+)+}})/g;
|
const variableRegExp = /({{step\.[\da-zA-Z-]+(?:\.[\da-zA-Z-_ :]+)+}})/g;
|
||||||
|
|
||||||
export default function computeParameters(
|
export default function computeParameters(
|
||||||
parameters: Step['parameters'],
|
parameters: Step['parameters'],
|
||||||
@@ -42,7 +42,7 @@ export default function computeParameters(
|
|||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
[key]: value.map(item => computeParameters(item, executionSteps)),
|
[key]: value.map((item) => computeParameters(item, executionSteps)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
packages/backend/src/helpers/create-auth-token-by-user-id.ts
Normal file
14
packages/backend/src/helpers/create-auth-token-by-user-id.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import appConfig from '../config/app';
|
||||||
|
|
||||||
|
const TOKEN_EXPIRES_IN = '14d';
|
||||||
|
|
||||||
|
const createAuthTokenByUserId = (userId: string) => {
|
||||||
|
const token = jwt.sign({ userId }, appConfig.appSecretKey, {
|
||||||
|
expiresIn: TOKEN_EXPIRES_IN,
|
||||||
|
});
|
||||||
|
|
||||||
|
return token;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createAuthTokenByUserId;
|
@@ -0,0 +1,48 @@
|
|||||||
|
import SamlAuthProvider from '../models/saml-auth-provider.ee';
|
||||||
|
import User from '../models/user';
|
||||||
|
import Identity from '../models/identity.ee';
|
||||||
|
|
||||||
|
const getUser = (user: Record<string, unknown>, providerConfig: SamlAuthProvider) => ({
|
||||||
|
name: user[providerConfig.firstnameAttributeName],
|
||||||
|
surname: user[providerConfig.surnameAttributeName],
|
||||||
|
id: user.nameID,
|
||||||
|
email: user[providerConfig.emailAttributeName],
|
||||||
|
role: user[providerConfig.roleAttributeName],
|
||||||
|
})
|
||||||
|
|
||||||
|
const findOrCreateUserBySamlIdentity = async (userIdentity: Record<string, unknown>, samlAuthProvider: SamlAuthProvider) => {
|
||||||
|
const mappedUser = getUser(userIdentity, samlAuthProvider);
|
||||||
|
const identity = await Identity.query().findOne({
|
||||||
|
remote_id: mappedUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (identity) {
|
||||||
|
const user = await identity.$relatedQuery('user');
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdUser = await User.query().insertGraph({
|
||||||
|
fullName: [
|
||||||
|
mappedUser.name,
|
||||||
|
mappedUser.surname
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' '),
|
||||||
|
email: mappedUser.email as string,
|
||||||
|
roleId: samlAuthProvider.defaultRoleId,
|
||||||
|
identities: [
|
||||||
|
{
|
||||||
|
remoteId: mappedUser.id as string,
|
||||||
|
providerId: samlAuthProvider.id,
|
||||||
|
providerType: 'saml'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
relate: ['identities']
|
||||||
|
}).returning('*');
|
||||||
|
|
||||||
|
return createdUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default findOrCreateUserBySamlIdentity;
|
@@ -1,8 +1,10 @@
|
|||||||
import axios, { AxiosRequestConfig } from 'axios';
|
|
||||||
export { AxiosInstance as IHttpClient } from 'axios';
|
|
||||||
import { IHttpClientParams } from '@automatisch/types';
|
import { IHttpClientParams } from '@automatisch/types';
|
||||||
import { URL } from 'url';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
|
import { URL } from 'node:url';
|
||||||
|
export { AxiosInstance as IHttpClient } from 'axios';
|
||||||
|
|
||||||
import HttpError from '../../errors/http';
|
import HttpError from '../../errors/http';
|
||||||
|
import axios from '../axios-with-proxy';
|
||||||
|
|
||||||
const removeBaseUrlForAbsoluteUrls = (
|
const removeBaseUrlForAbsoluteUrls = (
|
||||||
requestConfig: AxiosRequestConfig
|
requestConfig: AxiosRequestConfig
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { Model } from 'objection';
|
import { Model } from 'objection';
|
||||||
import ExtendedQueryBuilder from '../models/query-builder';
|
import ExtendedQueryBuilder from '../models/query-builder';
|
||||||
|
import type Base from '../models/base';
|
||||||
|
|
||||||
const paginate = async (
|
const paginate = async (
|
||||||
query: ExtendedQueryBuilder<Model, Model[]>,
|
query: ExtendedQueryBuilder<Model, Model[]>,
|
||||||
limit: number,
|
limit: number,
|
||||||
offset: number
|
offset: number,
|
||||||
) => {
|
) => {
|
||||||
if (limit < 1 || limit > 100) {
|
if (limit < 1 || limit > 100) {
|
||||||
throw new Error('Limit must be between 1 and 100');
|
throw new Error('Limit must be between 1 and 100');
|
||||||
@@ -20,11 +21,9 @@ const paginate = async (
|
|||||||
currentPage: Math.ceil(offset / limit + 1),
|
currentPage: Math.ceil(offset / limit + 1),
|
||||||
totalPages: Math.ceil(count / limit),
|
totalPages: Math.ceil(count / limit),
|
||||||
},
|
},
|
||||||
edges: records.map((record: Model) => {
|
edges: records.map((record: Base) => ({
|
||||||
return {
|
node: record,
|
||||||
node: record,
|
})),
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
84
packages/backend/src/helpers/passport.ts
Normal file
84
packages/backend/src/helpers/passport.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { URL } from 'node:url';
|
||||||
|
import { IRequest } from '@automatisch/types';
|
||||||
|
import { MultiSamlStrategy } from '@node-saml/passport-saml';
|
||||||
|
import { Express } from 'express';
|
||||||
|
import passport from 'passport';
|
||||||
|
|
||||||
|
import appConfig from '../config/app';
|
||||||
|
import createAuthTokenByUserId from '../helpers/create-auth-token-by-user-id';
|
||||||
|
import SamlAuthProvider from '../models/saml-auth-provider.ee';
|
||||||
|
import findOrCreateUserBySamlIdentity from './find-or-create-user-by-saml-identity.ee'
|
||||||
|
|
||||||
|
export default function configurePassport(app: Express) {
|
||||||
|
app.use(passport.initialize({
|
||||||
|
userProperty: 'currentUser',
|
||||||
|
}));
|
||||||
|
|
||||||
|
passport.use(new MultiSamlStrategy(
|
||||||
|
{
|
||||||
|
passReqToCallback: true,
|
||||||
|
getSamlOptions: async function (request, done) {
|
||||||
|
const { issuer } = request.params;
|
||||||
|
const notFoundIssuer = new Error('Issuer cannot be found!');
|
||||||
|
|
||||||
|
if (!issuer) return done(notFoundIssuer);
|
||||||
|
|
||||||
|
const authProvider = await SamlAuthProvider.query().findOne({
|
||||||
|
issuer: request.params.issuer as string,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!authProvider) {
|
||||||
|
return done(notFoundIssuer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return done(null, authProvider.config);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async function (request, user: Record<string, unknown>, done) {
|
||||||
|
const { issuer } = request.params;
|
||||||
|
const notFoundIssuer = new Error('Issuer cannot be found!');
|
||||||
|
|
||||||
|
if (!issuer) return done(notFoundIssuer);
|
||||||
|
|
||||||
|
const authProvider = await SamlAuthProvider.query().findOne({
|
||||||
|
issuer: request.params.issuer as string,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!authProvider) {
|
||||||
|
return done(notFoundIssuer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundUserWithIdentity = await findOrCreateUserBySamlIdentity(user, authProvider);
|
||||||
|
return done(null, foundUserWithIdentity as unknown as Record<string, unknown>);
|
||||||
|
},
|
||||||
|
function (request, user: Record<string, unknown>, done: (error: any, user: Record<string, unknown>) => void) {
|
||||||
|
return done(null, null);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
app.get('/login/saml/:issuer',
|
||||||
|
passport.authenticate('saml',
|
||||||
|
{
|
||||||
|
session: false,
|
||||||
|
successRedirect: '/',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
app.post(
|
||||||
|
'/login/saml/:issuer/callback',
|
||||||
|
passport.authenticate('saml', {
|
||||||
|
session: false,
|
||||||
|
failureRedirect: '/',
|
||||||
|
failureFlash: true,
|
||||||
|
}),
|
||||||
|
(req: IRequest, res) => {
|
||||||
|
const token = createAuthTokenByUserId(req.currentUser.id);
|
||||||
|
|
||||||
|
const redirectUrl = new URL(
|
||||||
|
`/login/callback?token=${token}`,
|
||||||
|
appConfig.webAppUrl,
|
||||||
|
).toString();
|
||||||
|
res.redirect(redirectUrl);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
72
packages/backend/src/helpers/permission-catalog.ee.ts
Normal file
72
packages/backend/src/helpers/permission-catalog.ee.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
const Connection = {
|
||||||
|
label: 'Connection',
|
||||||
|
key: 'Connection',
|
||||||
|
};
|
||||||
|
|
||||||
|
const Flow = {
|
||||||
|
label: 'Flow',
|
||||||
|
key: 'Flow',
|
||||||
|
};
|
||||||
|
|
||||||
|
const Execution = {
|
||||||
|
label: 'Execution',
|
||||||
|
key: 'Execution',
|
||||||
|
};
|
||||||
|
|
||||||
|
const permissionCatalog = {
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
key: 'isCreator',
|
||||||
|
label: 'Is creator'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: 'Create',
|
||||||
|
key: 'create',
|
||||||
|
subjects: [
|
||||||
|
Connection.key,
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Read',
|
||||||
|
key: 'read',
|
||||||
|
subjects: [
|
||||||
|
Connection.key,
|
||||||
|
Execution.key,
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Update',
|
||||||
|
key: 'update',
|
||||||
|
subjects: [
|
||||||
|
Connection.key,
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
key: 'delete',
|
||||||
|
subjects: [
|
||||||
|
Connection.key,
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Publish',
|
||||||
|
key: 'publish',
|
||||||
|
subjects: [
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
subjects: [
|
||||||
|
Connection,
|
||||||
|
Flow,
|
||||||
|
Execution
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export default permissionCatalog;
|
20
packages/backend/src/helpers/user-ability.ts
Normal file
20
packages/backend/src/helpers/user-ability.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { PureAbility, fieldPatternMatcher, mongoQueryMatcher } from '@casl/ability';
|
||||||
|
import type User from '../models/user'
|
||||||
|
|
||||||
|
// Must be kept in sync with `packages/web/src/helpers/userAbility.ts`!
|
||||||
|
export default function userAbility(user: Partial<User>) {
|
||||||
|
const permissions = user?.permissions;
|
||||||
|
const role = user?.role;
|
||||||
|
|
||||||
|
// We're not using mongo, but our fields, conditions match
|
||||||
|
const options = {
|
||||||
|
conditionsMatcher: mongoQueryMatcher,
|
||||||
|
fieldMatcher: fieldPatternMatcher
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!role || !permissions) {
|
||||||
|
return new PureAbility([], options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PureAbility<[string, string], string[]>(permissions, options);
|
||||||
|
}
|
@@ -40,6 +40,9 @@ class Connection extends Base {
|
|||||||
userId: { type: 'string', format: 'uuid' },
|
userId: { type: 'string', format: 'uuid' },
|
||||||
verified: { type: 'boolean', default: false },
|
verified: { type: 'boolean', default: false },
|
||||||
draft: { type: 'boolean' },
|
draft: { type: 'boolean' },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -31,6 +31,9 @@ class ExecutionStep extends Base {
|
|||||||
dataOut: { type: ['object', 'null'] },
|
dataOut: { type: ['object', 'null'] },
|
||||||
status: { type: 'string', enum: ['success', 'failure'] },
|
status: { type: 'string', enum: ['success', 'failure'] },
|
||||||
errorDetails: { type: ['object', 'null'] },
|
errorDetails: { type: ['object', 'null'] },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -22,6 +22,9 @@ class Execution extends Base {
|
|||||||
flowId: { type: 'string', format: 'uuid' },
|
flowId: { type: 'string', format: 'uuid' },
|
||||||
testRun: { type: 'boolean', default: false },
|
testRun: { type: 'boolean', default: false },
|
||||||
internalId: { type: 'string' },
|
internalId: { type: 'string' },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -19,7 +19,7 @@ class Flow extends Base {
|
|||||||
status: 'paused' | 'published' | 'draft';
|
status: 'paused' | 'published' | 'draft';
|
||||||
steps: Step[];
|
steps: Step[];
|
||||||
triggerStep: Step;
|
triggerStep: Step;
|
||||||
published_at: string;
|
publishedAt: string;
|
||||||
remoteWebhookId: string;
|
remoteWebhookId: string;
|
||||||
executions?: Execution[];
|
executions?: Execution[];
|
||||||
lastExecution?: Execution;
|
lastExecution?: Execution;
|
||||||
@@ -37,6 +37,10 @@ class Flow extends Base {
|
|||||||
userId: { type: 'string', format: 'uuid' },
|
userId: { type: 'string', format: 'uuid' },
|
||||||
remoteWebhookId: { type: 'string' },
|
remoteWebhookId: { type: 'string' },
|
||||||
active: { type: 'boolean' },
|
active: { type: 'boolean' },
|
||||||
|
publishedAt: { type: 'string' },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
53
packages/backend/src/models/identity.ee.ts
Normal file
53
packages/backend/src/models/identity.ee.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import Base from './base';
|
||||||
|
import SamlAuthProvider from './saml-auth-provider.ee';
|
||||||
|
import User from './user';
|
||||||
|
|
||||||
|
class Identity extends Base {
|
||||||
|
id!: string;
|
||||||
|
remoteId!: string;
|
||||||
|
userId!: string;
|
||||||
|
providerId!: string;
|
||||||
|
providerType!: 'saml';
|
||||||
|
|
||||||
|
static tableName = 'identities';
|
||||||
|
|
||||||
|
static jsonSchema = {
|
||||||
|
type: 'object',
|
||||||
|
required: [
|
||||||
|
'providerId',
|
||||||
|
'remoteId',
|
||||||
|
'userId',
|
||||||
|
'providerType',
|
||||||
|
],
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string', format: 'uuid' },
|
||||||
|
userId: { type: 'string', format: 'uuid' },
|
||||||
|
remoteId: { type: 'string', minLength: 1 },
|
||||||
|
providerId: { type: 'string', format: 'uuid' },
|
||||||
|
providerType: { type: 'string', enum: ['saml'] },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static relationMappings = () => ({
|
||||||
|
user: {
|
||||||
|
relation: Base.BelongsToOneRelation,
|
||||||
|
modelClass: User,
|
||||||
|
join: {
|
||||||
|
from: 'users.id',
|
||||||
|
to: 'identities.user_id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
samlAuthProvider: {
|
||||||
|
relation: Base.BelongsToOneRelation,
|
||||||
|
modelClass: SamlAuthProvider,
|
||||||
|
join: {
|
||||||
|
from: 'saml_auth_providers.id',
|
||||||
|
to: 'identities.provider_id'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Identity;
|
28
packages/backend/src/models/permission.ts
Normal file
28
packages/backend/src/models/permission.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import Base from './base';
|
||||||
|
|
||||||
|
class Permission extends Base {
|
||||||
|
id: string;
|
||||||
|
roleId: string;
|
||||||
|
action: string;
|
||||||
|
subject: string;
|
||||||
|
conditions: string[];
|
||||||
|
|
||||||
|
static tableName = 'permissions';
|
||||||
|
|
||||||
|
static jsonSchema = {
|
||||||
|
type: 'object',
|
||||||
|
required: ['roleId', 'action', 'subject'],
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string', format: 'uuid' },
|
||||||
|
roleId: { type: 'string', format: 'uuid' },
|
||||||
|
action: { type: 'string', minLength: 1 },
|
||||||
|
subject: { type: 'string', minLength: 1 },
|
||||||
|
conditions: { type: 'array', items: { type: 'string' } },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Permission;
|
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Model,
|
Model,
|
||||||
Page,
|
Page,
|
||||||
|
ModelClass,
|
||||||
PartialModelObject,
|
PartialModelObject,
|
||||||
ForClassMethod,
|
ForClassMethod,
|
||||||
AnyQueryBuilder,
|
AnyQueryBuilder,
|
||||||
@@ -8,6 +9,10 @@ import {
|
|||||||
|
|
||||||
const DELETED_COLUMN_NAME = 'deleted_at';
|
const DELETED_COLUMN_NAME = 'deleted_at';
|
||||||
|
|
||||||
|
const supportsSoftDeletion = (modelClass: ModelClass<any>) => {
|
||||||
|
return modelClass.jsonSchema.properties.deletedAt;
|
||||||
|
}
|
||||||
|
|
||||||
const buildQueryBuidlerForClass = (): ForClassMethod => {
|
const buildQueryBuidlerForClass = (): ForClassMethod => {
|
||||||
return (modelClass) => {
|
return (modelClass) => {
|
||||||
const qb: AnyQueryBuilder = Model.QueryBuilder.forClass.call(
|
const qb: AnyQueryBuilder = Model.QueryBuilder.forClass.call(
|
||||||
@@ -15,7 +20,7 @@ const buildQueryBuidlerForClass = (): ForClassMethod => {
|
|||||||
modelClass
|
modelClass
|
||||||
);
|
);
|
||||||
qb.onBuild((builder) => {
|
qb.onBuild((builder) => {
|
||||||
if (!builder.context().withSoftDeleted) {
|
if (!builder.context().withSoftDeleted && supportsSoftDeletion(qb.modelClass())) {
|
||||||
builder.whereNull(
|
builder.whereNull(
|
||||||
`${qb.modelClass().tableName}.${DELETED_COLUMN_NAME}`
|
`${qb.modelClass().tableName}.${DELETED_COLUMN_NAME}`
|
||||||
);
|
);
|
||||||
@@ -38,9 +43,13 @@ class ExtendedQueryBuilder<M extends Model, R = M[]> extends Model.QueryBuilder<
|
|||||||
static forClass: ForClassMethod = buildQueryBuidlerForClass();
|
static forClass: ForClassMethod = buildQueryBuidlerForClass();
|
||||||
|
|
||||||
delete() {
|
delete() {
|
||||||
return this.patch({
|
if (supportsSoftDeletion(this.modelClass())) {
|
||||||
[DELETED_COLUMN_NAME]: new Date().toISOString(),
|
return this.patch({
|
||||||
} as unknown as PartialModelObject<M>);
|
[DELETED_COLUMN_NAME]: new Date().toISOString(),
|
||||||
|
} as unknown as PartialModelObject<M>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
hardDelete() {
|
hardDelete() {
|
||||||
|
57
packages/backend/src/models/role.ts
Normal file
57
packages/backend/src/models/role.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import Base from './base';
|
||||||
|
import Permission from './permission';
|
||||||
|
import User from './user';
|
||||||
|
|
||||||
|
class Role extends Base {
|
||||||
|
id!: string;
|
||||||
|
name!: string;
|
||||||
|
key: string;
|
||||||
|
description: string;
|
||||||
|
users?: User[];
|
||||||
|
permissions?: Permission[];
|
||||||
|
|
||||||
|
static tableName = 'roles';
|
||||||
|
|
||||||
|
static jsonSchema = {
|
||||||
|
type: 'object',
|
||||||
|
required: ['name', 'key'],
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string', format: 'uuid' },
|
||||||
|
name: { type: 'string', minLength: 1 },
|
||||||
|
key: { type: 'string', minLength: 1 },
|
||||||
|
description: { type: ['string', 'null'], maxLength: 255 },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static get virtualAttributes() {
|
||||||
|
return ['isAdmin'];
|
||||||
|
}
|
||||||
|
|
||||||
|
static relationMappings = () => ({
|
||||||
|
users: {
|
||||||
|
relation: Base.HasManyRelation,
|
||||||
|
modelClass: User,
|
||||||
|
join: {
|
||||||
|
from: 'roles.id',
|
||||||
|
to: 'users.role_id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
relation: Base.HasManyRelation,
|
||||||
|
modelClass: Permission,
|
||||||
|
join: {
|
||||||
|
from: 'roles.id',
|
||||||
|
to: 'permissions.role_id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
get isAdmin() {
|
||||||
|
return this.key === 'admin';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Role;
|
84
packages/backend/src/models/saml-auth-provider.ee.ts
Normal file
84
packages/backend/src/models/saml-auth-provider.ee.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { URL } from 'node:url';
|
||||||
|
import type { SamlConfig } from '@node-saml/passport-saml';
|
||||||
|
import appConfig from '../config/app';
|
||||||
|
import Base from './base';
|
||||||
|
import Identity from './identity.ee';
|
||||||
|
|
||||||
|
class SamlAuthProvider extends Base {
|
||||||
|
id!: string;
|
||||||
|
name: string;
|
||||||
|
certificate: string;
|
||||||
|
signatureAlgorithm: SamlConfig['signatureAlgorithm'];
|
||||||
|
issuer: string;
|
||||||
|
entryPoint: string;
|
||||||
|
firstnameAttributeName: string;
|
||||||
|
surnameAttributeName: string;
|
||||||
|
emailAttributeName: string;
|
||||||
|
roleAttributeName: string;
|
||||||
|
defaultRoleId: string;
|
||||||
|
active: boolean;
|
||||||
|
|
||||||
|
static tableName = 'saml_auth_providers';
|
||||||
|
|
||||||
|
static jsonSchema = {
|
||||||
|
type: 'object',
|
||||||
|
required: [
|
||||||
|
'name',
|
||||||
|
'certificate',
|
||||||
|
'signatureAlgorithm',
|
||||||
|
'entryPoint',
|
||||||
|
'issuer',
|
||||||
|
'firstnameAttributeName',
|
||||||
|
'surnameAttributeName',
|
||||||
|
'emailAttributeName',
|
||||||
|
'roleAttributeName',
|
||||||
|
'defaultRoleId',
|
||||||
|
],
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string', format: 'uuid' },
|
||||||
|
name: { type: 'string', minLength: 1 },
|
||||||
|
certificate: { type: 'string', minLength: 1 },
|
||||||
|
signatureAlgorithm: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['sha1', 'sha256', 'sha512'],
|
||||||
|
},
|
||||||
|
issuer: { type: 'string', minLength: 1 },
|
||||||
|
entryPoint: { type: 'string', minLength: 1 },
|
||||||
|
firstnameAttributeName: { type: 'string', minLength: 1 },
|
||||||
|
surnameAttributeName: { type: 'string', minLength: 1 },
|
||||||
|
emailAttributeName: { type: 'string', minLength: 1 },
|
||||||
|
roleAttributeName: { type: 'string', minLength: 1 },
|
||||||
|
defaultRoleId: { type: 'string', format: 'uuid' },
|
||||||
|
active: { type: 'boolean' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static relationMappings = () => ({
|
||||||
|
identities: {
|
||||||
|
relation: Base.HasOneRelation,
|
||||||
|
modelClass: Identity,
|
||||||
|
join: {
|
||||||
|
from: 'identities.provider_id',
|
||||||
|
to: 'saml_auth_providers.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
get config(): SamlConfig {
|
||||||
|
const callbackUrl = new URL(
|
||||||
|
`/login/saml/${this.issuer}/callback`,
|
||||||
|
appConfig.baseUrl
|
||||||
|
).toString();
|
||||||
|
|
||||||
|
return {
|
||||||
|
callbackUrl,
|
||||||
|
cert: this.certificate,
|
||||||
|
entryPoint: this.entryPoint,
|
||||||
|
issuer: this.issuer,
|
||||||
|
signatureAlgorithm: this.signatureAlgorithm,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SamlAuthProvider;
|
@@ -46,6 +46,9 @@ class Step extends Base {
|
|||||||
position: { type: 'integer' },
|
position: { type: 'integer' },
|
||||||
parameters: { type: 'object' },
|
parameters: { type: 'object' },
|
||||||
webhookPath: { type: ['string', 'null'] },
|
webhookPath: { type: ['string', 'null'] },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -46,6 +46,9 @@ class Subscription extends Base {
|
|||||||
nextBillDate: { type: 'string' },
|
nextBillDate: { type: 'string' },
|
||||||
lastBillDate: { type: 'string' },
|
lastBillDate: { type: 'string' },
|
||||||
cancellationEffectiveDate: { type: 'string' },
|
cancellationEffectiveDate: { type: 'string' },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,7 +87,7 @@ class Subscription extends Base {
|
|||||||
return (
|
return (
|
||||||
this.status === 'deleted' &&
|
this.status === 'deleted' &&
|
||||||
Number(this.cancellationEffectiveDate) >
|
Number(this.cancellationEffectiveDate) >
|
||||||
DateTime.now().startOf('day').toMillis()
|
DateTime.now().startOf('day').toMillis()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -24,6 +24,9 @@ class UsageData extends Base {
|
|||||||
subscriptionId: { type: 'string', format: 'uuid' },
|
subscriptionId: { type: 'string', format: 'uuid' },
|
||||||
consumedTaskCount: { type: 'integer' },
|
consumedTaskCount: { type: 'integer' },
|
||||||
nextResetAt: { type: 'string' },
|
nextResetAt: { type: 'string' },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user