feat: Convert helpers to use JS files
This commit is contained in:
@@ -1,6 +1,4 @@
|
|||||||
import { IApp } from '@automatisch/types';
|
function addAuthenticationSteps(app) {
|
||||||
|
|
||||||
function addAuthenticationSteps(app: IApp): IApp {
|
|
||||||
if (app.auth.generateAuthUrl) {
|
if (app.auth.generateAuthUrl) {
|
||||||
app.auth.authenticationSteps = authenticationStepsWithAuthUrl;
|
app.auth.authenticationSteps = authenticationStepsWithAuthUrl;
|
||||||
app.auth.sharedAuthenticationSteps = sharedAuthenticationStepsWithAuthUrl;
|
app.auth.sharedAuthenticationSteps = sharedAuthenticationStepsWithAuthUrl;
|
||||||
@@ -13,7 +11,7 @@ function addAuthenticationSteps(app: IApp): IApp {
|
|||||||
|
|
||||||
const authenticationStepsWithoutAuthUrl = [
|
const authenticationStepsWithoutAuthUrl = [
|
||||||
{
|
{
|
||||||
type: 'mutation' as const,
|
type: 'mutation',
|
||||||
name: 'createConnection',
|
name: 'createConnection',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
@@ -27,7 +25,7 @@ const authenticationStepsWithoutAuthUrl = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'mutation' as const,
|
type: 'mutation',
|
||||||
name: 'verifyConnection',
|
name: 'verifyConnection',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
@@ -40,7 +38,7 @@ const authenticationStepsWithoutAuthUrl = [
|
|||||||
|
|
||||||
const authenticationStepsWithAuthUrl = [
|
const authenticationStepsWithAuthUrl = [
|
||||||
{
|
{
|
||||||
type: 'mutation' as const,
|
type: 'mutation',
|
||||||
name: 'createConnection',
|
name: 'createConnection',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
@@ -54,7 +52,7 @@ const authenticationStepsWithAuthUrl = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'mutation' as const,
|
type: 'mutation',
|
||||||
name: 'generateAuthUrl',
|
name: 'generateAuthUrl',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
@@ -64,7 +62,7 @@ const authenticationStepsWithAuthUrl = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'openWithPopup' as const,
|
type: 'openWithPopup',
|
||||||
name: 'openAuthPopup',
|
name: 'openAuthPopup',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
@@ -74,7 +72,7 @@ const authenticationStepsWithAuthUrl = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'mutation' as const,
|
type: 'mutation',
|
||||||
name: 'updateConnection',
|
name: 'updateConnection',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
@@ -88,7 +86,7 @@ const authenticationStepsWithAuthUrl = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'mutation' as const,
|
type: 'mutation',
|
||||||
name: 'verifyConnection',
|
name: 'verifyConnection',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
@@ -101,7 +99,7 @@ const authenticationStepsWithAuthUrl = [
|
|||||||
|
|
||||||
const sharedAuthenticationStepsWithAuthUrl = [
|
const sharedAuthenticationStepsWithAuthUrl = [
|
||||||
{
|
{
|
||||||
type: 'mutation' as const,
|
type: 'mutation',
|
||||||
name: 'createConnection',
|
name: 'createConnection',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
@@ -115,7 +113,7 @@ const sharedAuthenticationStepsWithAuthUrl = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'mutation' as const,
|
type: 'mutation',
|
||||||
name: 'generateAuthUrl',
|
name: 'generateAuthUrl',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
@@ -125,7 +123,7 @@ const sharedAuthenticationStepsWithAuthUrl = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'openWithPopup' as const,
|
type: 'openWithPopup',
|
||||||
name: 'openAuthPopup',
|
name: 'openAuthPopup',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
@@ -135,7 +133,7 @@ const sharedAuthenticationStepsWithAuthUrl = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'mutation' as const,
|
type: 'mutation',
|
||||||
name: 'updateConnection',
|
name: 'updateConnection',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
@@ -149,7 +147,7 @@ const sharedAuthenticationStepsWithAuthUrl = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'mutation' as const,
|
type: 'mutation',
|
||||||
name: 'verifyConnection',
|
name: 'verifyConnection',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
@@ -1,8 +1,3 @@
|
|||||||
import {
|
|
||||||
IApp,
|
|
||||||
IAuthenticationStep,
|
|
||||||
IAuthenticationStepField,
|
|
||||||
} from '@automatisch/types';
|
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
|
||||||
const connectionIdArgument = {
|
const connectionIdArgument = {
|
||||||
@@ -11,20 +6,20 @@ const connectionIdArgument = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const resetConnectionStep = {
|
const resetConnectionStep = {
|
||||||
type: 'mutation' as const,
|
type: 'mutation',
|
||||||
name: 'resetConnection',
|
name: 'resetConnection',
|
||||||
arguments: [connectionIdArgument],
|
arguments: [connectionIdArgument],
|
||||||
};
|
};
|
||||||
|
|
||||||
function replaceCreateConnection(string: string) {
|
function replaceCreateConnection(string) {
|
||||||
return string.replace('{createConnection.id}', '{connection.id}');
|
return string.replace('{createConnection.id}', '{connection.id}');
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeAppKeyArgument(args: IAuthenticationStepField[]) {
|
function removeAppKeyArgument(args) {
|
||||||
return args.filter((argument) => argument.name !== 'key');
|
return args.filter((argument) => argument.name !== 'key');
|
||||||
}
|
}
|
||||||
|
|
||||||
function addConnectionId(step: IAuthenticationStep) {
|
function addConnectionId(step) {
|
||||||
step.arguments = step.arguments.map((argument) => {
|
step.arguments = step.arguments.map((argument) => {
|
||||||
if (typeof argument.value === 'string') {
|
if (typeof argument.value === 'string') {
|
||||||
argument.value = replaceCreateConnection(argument.value);
|
argument.value = replaceCreateConnection(argument.value);
|
||||||
@@ -45,7 +40,7 @@ function addConnectionId(step: IAuthenticationStep) {
|
|||||||
return step;
|
return step;
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceCreateConnectionsWithUpdate(steps: IAuthenticationStep[]) {
|
function replaceCreateConnectionsWithUpdate(steps) {
|
||||||
const updatedSteps = cloneDeep(steps);
|
const updatedSteps = cloneDeep(steps);
|
||||||
return updatedSteps.map((step) => {
|
return updatedSteps.map((step) => {
|
||||||
const updatedStep = addConnectionId(step);
|
const updatedStep = addConnectionId(step);
|
||||||
@@ -62,7 +57,7 @@ function replaceCreateConnectionsWithUpdate(steps: IAuthenticationStep[]) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addReconnectionSteps(app: IApp): IApp {
|
function addReconnectionSteps(app) {
|
||||||
const hasReconnectionSteps = app.auth.reconnectionSteps;
|
const hasReconnectionSteps = app.auth.reconnectionSteps;
|
||||||
|
|
||||||
if (hasReconnectionSteps) return app;
|
if (hasReconnectionSteps) return app;
|
||||||
@@ -80,7 +75,10 @@ function addReconnectionSteps(app: IApp): IApp {
|
|||||||
app.auth.sharedAuthenticationSteps
|
app.auth.sharedAuthenticationSteps
|
||||||
);
|
);
|
||||||
|
|
||||||
app.auth.sharedReconnectionSteps = [resetConnectionStep, ...updatedStepsWithEmbeddedDefaults];
|
app.auth.sharedReconnectionSteps = [
|
||||||
|
resetConnectionStep,
|
||||||
|
...updatedStepsWithEmbeddedDefaults,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return app;
|
return app;
|
@@ -1,6 +1,6 @@
|
|||||||
import express, { Application } from 'express';
|
import express from 'express';
|
||||||
|
|
||||||
const appAssetsHandler = async (app: Application) => {
|
const appAssetsHandler = async (app) => {
|
||||||
app.use('/apps/:appKey/assets/favicon.svg', (req, res, next) => {
|
app.use('/apps/:appKey/assets/favicon.svg', (req, res, next) => {
|
||||||
const { appKey } = req.params;
|
const { appKey } = req.params;
|
||||||
const svgPath = `${__dirname}/../apps/${appKey}/assets/favicon.svg`;
|
const svgPath = `${__dirname}/../apps/${appKey}/assets/favicon.svg`;
|
@@ -1,7 +1,6 @@
|
|||||||
import type { IApp } from '@automatisch/types';
|
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
|
|
||||||
const appInfoConverter = (rawAppData: IApp) => {
|
const appInfoConverter = (rawAppData) => {
|
||||||
rawAppData.iconUrl = rawAppData.iconUrl.replace(
|
rawAppData.iconUrl = rawAppData.iconUrl.replace(
|
||||||
'{BASE_URL}',
|
'{BASE_URL}',
|
||||||
appConfig.baseUrl
|
appConfig.baseUrl
|
@@ -9,9 +9,8 @@ const isAuthenticated = rule()(async (_parent, _args, req) => {
|
|||||||
if (token == null) return false;
|
if (token == null) return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { userId } = jwt.verify(token, appConfig.appSecretKey) as {
|
const { userId } = jwt.verify(token, appConfig.appSecretKey);
|
||||||
userId: string;
|
|
||||||
};
|
|
||||||
req.currentUser = await User.query()
|
req.currentUser = await User.query()
|
||||||
.findById(userId)
|
.findById(userId)
|
||||||
.leftJoinRelated({
|
.leftJoinRelated({
|
@@ -1,8 +1,8 @@
|
|||||||
import axios, { AxiosRequestConfig } from 'axios';
|
import axios from 'axios';
|
||||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||||
import { HttpProxyAgent } from 'http-proxy-agent';
|
import { HttpProxyAgent } from 'http-proxy-agent';
|
||||||
|
|
||||||
const config: AxiosRequestConfig = {};
|
const config = {};
|
||||||
const httpProxyUrl = process.env.http_proxy;
|
const httpProxyUrl = process.env.http_proxy;
|
||||||
const httpsProxyUrl = process.env.https_proxy;
|
const httpsProxyUrl = process.env.https_proxy;
|
||||||
const supportsProxy = httpProxyUrl || httpsProxyUrl;
|
const supportsProxy = httpProxyUrl || httpsProxyUrl;
|
@@ -9,7 +9,7 @@ const PADDLE_VENDOR_URL = appConfig.isDev
|
|||||||
|
|
||||||
const axiosInstance = axios.create({ baseURL: PADDLE_VENDOR_URL });
|
const axiosInstance = axios.create({ baseURL: PADDLE_VENDOR_URL });
|
||||||
|
|
||||||
const getSubscription = async (subscriptionId: number) => {
|
const getSubscription = async (subscriptionId) => {
|
||||||
const data = {
|
const data = {
|
||||||
vendor_id: appConfig.paddleVendorId,
|
vendor_id: appConfig.paddleVendorId,
|
||||||
vendor_auth_code: appConfig.paddleVendorAuthCode,
|
vendor_auth_code: appConfig.paddleVendorAuthCode,
|
||||||
@@ -24,7 +24,7 @@ const getSubscription = async (subscriptionId: number) => {
|
|||||||
return subscription;
|
return subscription;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getInvoices = async (subscriptionId: number) => {
|
const getInvoices = async (subscriptionId) => {
|
||||||
// TODO: iterate over previous subscriptions and include their invoices
|
// TODO: iterate over previous subscriptions and include their invoices
|
||||||
const data = {
|
const data = {
|
||||||
vendor_id: appConfig.paddleVendorId,
|
vendor_id: appConfig.paddleVendorId,
|
@@ -22,7 +22,7 @@ const prodPlans = [
|
|||||||
|
|
||||||
const plans = appConfig.isProd ? prodPlans : testPlans;
|
const plans = appConfig.isProd ? prodPlans : testPlans;
|
||||||
|
|
||||||
export function getPlanById(id: string) {
|
export function getPlanById(id) {
|
||||||
return plans.find((plan) => plan.productId === id);
|
return plans.find((plan) => plan.productId === id);
|
||||||
}
|
}
|
||||||
|
|
@@ -1,8 +1,7 @@
|
|||||||
import { IRequest } from '@automatisch/types';
|
|
||||||
import Subscription from '../../models/subscription.ee';
|
import Subscription from '../../models/subscription.ee';
|
||||||
import Billing from './index.ee';
|
import Billing from './index.ee';
|
||||||
|
|
||||||
const handleSubscriptionCreated = async (request: IRequest) => {
|
const handleSubscriptionCreated = async (request) => {
|
||||||
const subscription = await Subscription.query().insertAndFetch(
|
const subscription = await Subscription.query().insertAndFetch(
|
||||||
formatSubscription(request)
|
formatSubscription(request)
|
||||||
);
|
);
|
||||||
@@ -11,7 +10,7 @@ const handleSubscriptionCreated = async (request: IRequest) => {
|
|||||||
.insert(formatUsageData(request));
|
.insert(formatUsageData(request));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubscriptionUpdated = async (request: IRequest) => {
|
const handleSubscriptionUpdated = async (request) => {
|
||||||
await Subscription.query()
|
await Subscription.query()
|
||||||
.findOne({
|
.findOne({
|
||||||
paddle_subscription_id: request.body.subscription_id,
|
paddle_subscription_id: request.body.subscription_id,
|
||||||
@@ -19,7 +18,7 @@ const handleSubscriptionUpdated = async (request: IRequest) => {
|
|||||||
.patch(formatSubscription(request));
|
.patch(formatSubscription(request));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubscriptionCancelled = async (request: IRequest) => {
|
const handleSubscriptionCancelled = async (request) => {
|
||||||
const subscription = await Subscription.query().findOne({
|
const subscription = await Subscription.query().findOne({
|
||||||
paddle_subscription_id: request.body.subscription_id,
|
paddle_subscription_id: request.body.subscription_id,
|
||||||
});
|
});
|
||||||
@@ -27,7 +26,7 @@ const handleSubscriptionCancelled = async (request: IRequest) => {
|
|||||||
await subscription.$query().patchAndFetch(formatSubscription(request));
|
await subscription.$query().patchAndFetch(formatSubscription(request));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubscriptionPaymentSucceeded = async (request: IRequest) => {
|
const handleSubscriptionPaymentSucceeded = async (request) => {
|
||||||
const subscription = await Subscription.query()
|
const subscription = await Subscription.query()
|
||||||
.findOne({
|
.findOne({
|
||||||
paddle_subscription_id: request.body.subscription_id,
|
paddle_subscription_id: request.body.subscription_id,
|
||||||
@@ -49,7 +48,7 @@ const handleSubscriptionPaymentSucceeded = async (request: IRequest) => {
|
|||||||
.insert(formatUsageData(request));
|
.insert(formatUsageData(request));
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatSubscription = (request: IRequest) => {
|
const formatSubscription = (request) => {
|
||||||
return {
|
return {
|
||||||
userId: JSON.parse(request.body.passthrough).id,
|
userId: JSON.parse(request.body.passthrough).id,
|
||||||
paddleSubscriptionId: request.body.subscription_id,
|
paddleSubscriptionId: request.body.subscription_id,
|
||||||
@@ -63,7 +62,7 @@ const formatSubscription = (request: IRequest) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatUsageData = (request: IRequest) => {
|
const formatUsageData = (request) => {
|
||||||
return {
|
return {
|
||||||
userId: JSON.parse(request.body.passthrough).id,
|
userId: JSON.parse(request.body.passthrough).id,
|
||||||
consumedTaskCount: 0,
|
consumedTaskCount: 0,
|
@@ -2,7 +2,7 @@ import * as path from 'path';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as handlebars from 'handlebars';
|
import * as handlebars from 'handlebars';
|
||||||
|
|
||||||
const compileEmail = (emailPath: string, replacements: object = {}): string => {
|
const compileEmail = (emailPath, replacements = {}) => {
|
||||||
const filePath = path.join(__dirname, `../views/emails/${emailPath}.ee.hbs`);
|
const filePath = path.join(__dirname, `../views/emails/${emailPath}.ee.hbs`);
|
||||||
const source = fs.readFileSync(filePath, 'utf-8').toString();
|
const source = fs.readFileSync(filePath, 'utf-8').toString();
|
||||||
const template = handlebars.compile(source);
|
const template = handlebars.compile(source);
|
@@ -1,23 +1,18 @@
|
|||||||
import Step from '../models/step';
|
|
||||||
import ExecutionStep from '../models/execution-step';
|
|
||||||
import get from 'lodash.get';
|
import get from 'lodash.get';
|
||||||
|
|
||||||
const variableRegExp = /({{step\.[\da-zA-Z-]+(?:\.[^.}{]+)+}})/g;
|
const variableRegExp = /({{step\.[\da-zA-Z-]+(?:\.[^.}{]+)+}})/g;
|
||||||
|
|
||||||
export default function computeParameters(
|
export default function computeParameters(parameters, executionSteps) {
|
||||||
parameters: Step['parameters'],
|
|
||||||
executionSteps: ExecutionStep[]
|
|
||||||
): Step['parameters'] {
|
|
||||||
const entries = Object.entries(parameters);
|
const entries = Object.entries(parameters);
|
||||||
return entries.reduce((result, [key, value]: [string, unknown]) => {
|
return entries.reduce((result, [key, value]) => {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
const parts = value.split(variableRegExp);
|
const parts = value.split(variableRegExp);
|
||||||
|
|
||||||
const computedValue = parts
|
const computedValue = parts
|
||||||
.map((part: string) => {
|
.map((part) => {
|
||||||
const isVariable = part.match(variableRegExp);
|
const isVariable = part.match(variableRegExp);
|
||||||
if (isVariable) {
|
if (isVariable) {
|
||||||
const stepIdAndKeyPath = part.replace(/{{step.|}}/g, '') as string;
|
const stepIdAndKeyPath = part.replace(/{{step.|}}/g, '');
|
||||||
const [stepId, ...keyPaths] = stepIdAndKeyPath.split('.');
|
const [stepId, ...keyPaths] = stepIdAndKeyPath.split('.');
|
||||||
const keyPath = keyPaths.join('.');
|
const keyPath = keyPaths.join('.');
|
||||||
const executionStep = executionSteps.find((executionStep) => {
|
const executionStep = executionSteps.find((executionStep) => {
|
@@ -3,7 +3,7 @@ import appConfig from '../config/app';
|
|||||||
|
|
||||||
const TOKEN_EXPIRES_IN = '14d';
|
const TOKEN_EXPIRES_IN = '14d';
|
||||||
|
|
||||||
const createAuthTokenByUserId = (userId: string) => {
|
const createAuthTokenByUserId = (userId) => {
|
||||||
const token = jwt.sign({ userId }, appConfig.appSecretKey, {
|
const token = jwt.sign({ userId }, appConfig.appSecretKey, {
|
||||||
expiresIn: TOKEN_EXPIRES_IN,
|
expiresIn: TOKEN_EXPIRES_IN,
|
||||||
});
|
});
|
@@ -31,7 +31,7 @@ const shouldEnableBullDashboard = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createBullBoardHandler = async (serverAdapter: ExpressAdapter) => {
|
const createBullBoardHandler = async (serverAdapter) => {
|
||||||
if (!shouldEnableBullDashboard) return;
|
if (!shouldEnableBullDashboard) return;
|
||||||
|
|
||||||
createBullBoard({
|
createBullBoard({
|
3
packages/backend/src/helpers/define-action.js
Normal file
3
packages/backend/src/helpers/define-action.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default function defineAction(actionDefinition) {
|
||||||
|
return actionDefinition;
|
||||||
|
}
|
@@ -1,5 +0,0 @@
|
|||||||
import { IRawAction } from '@automatisch/types';
|
|
||||||
|
|
||||||
export default function defineAction(actionDefinition: IRawAction): IRawAction {
|
|
||||||
return actionDefinition;
|
|
||||||
}
|
|
3
packages/backend/src/helpers/define-app.js
Normal file
3
packages/backend/src/helpers/define-app.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default function defineApp(appDefinition) {
|
||||||
|
return appDefinition;
|
||||||
|
}
|
@@ -1,5 +0,0 @@
|
|||||||
import { IApp } from '@automatisch/types';
|
|
||||||
|
|
||||||
export default function defineApp(appDefinition: IApp): IApp {
|
|
||||||
return appDefinition;
|
|
||||||
}
|
|
3
packages/backend/src/helpers/define-trigger.js
Normal file
3
packages/backend/src/helpers/define-trigger.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default function defineTrigger(triggerDefinition) {
|
||||||
|
return triggerDefinition;
|
||||||
|
}
|
@@ -1,7 +0,0 @@
|
|||||||
import { IRawTrigger } from '@automatisch/types';
|
|
||||||
|
|
||||||
export default function defineTrigger(
|
|
||||||
triggerDefinition: IRawTrigger
|
|
||||||
): IRawTrigger {
|
|
||||||
return triggerDefinition;
|
|
||||||
}
|
|
19
packages/backend/src/helpers/delay-as-milliseconds.js
Normal file
19
packages/backend/src/helpers/delay-as-milliseconds.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import delayForAsMilliseconds from './delay-for-as-milliseconds';
|
||||||
|
import delayUntilAsMilliseconds from './delay-until-as-milliseconds';
|
||||||
|
|
||||||
|
const delayAsMilliseconds = (eventKey, computedParameters) => {
|
||||||
|
let delayDuration = 0;
|
||||||
|
|
||||||
|
if (eventKey === 'delayFor') {
|
||||||
|
const { delayForUnit, delayForValue } = computedParameters;
|
||||||
|
|
||||||
|
delayDuration = delayForAsMilliseconds(delayForUnit, Number(delayForValue));
|
||||||
|
} else if (eventKey === 'delayUntil') {
|
||||||
|
const { delayUntil } = computedParameters;
|
||||||
|
delayDuration = delayUntilAsMilliseconds(delayUntil);
|
||||||
|
}
|
||||||
|
|
||||||
|
return delayDuration;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default delayAsMilliseconds;
|
@@ -1,25 +0,0 @@
|
|||||||
import Step from '../models/step';
|
|
||||||
import delayForAsMilliseconds, {
|
|
||||||
TDelayForUnit,
|
|
||||||
} from './delay-for-as-milliseconds';
|
|
||||||
import delayUntilAsMilliseconds from './delay-until-as-milliseconds';
|
|
||||||
|
|
||||||
const delayAsMilliseconds = (eventKey: Step["key"], computedParameters: Step["parameters"]) => {
|
|
||||||
let delayDuration = 0;
|
|
||||||
|
|
||||||
if (eventKey === 'delayFor') {
|
|
||||||
const { delayForUnit, delayForValue } = computedParameters;
|
|
||||||
|
|
||||||
delayDuration = delayForAsMilliseconds(
|
|
||||||
delayForUnit as TDelayForUnit,
|
|
||||||
Number(delayForValue)
|
|
||||||
);
|
|
||||||
} else if (eventKey === 'delayUntil') {
|
|
||||||
const { delayUntil } = computedParameters;
|
|
||||||
delayDuration = delayUntilAsMilliseconds(delayUntil as string);
|
|
||||||
}
|
|
||||||
|
|
||||||
return delayDuration;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default delayAsMilliseconds;
|
|
@@ -1,9 +1,4 @@
|
|||||||
export type TDelayForUnit = 'minutes' | 'hours' | 'days' | 'weeks';
|
const delayAsMilliseconds = (delayForUnit, delayForValue) => {
|
||||||
|
|
||||||
const delayAsMilliseconds = (
|
|
||||||
delayForUnit: TDelayForUnit,
|
|
||||||
delayForValue: number
|
|
||||||
) => {
|
|
||||||
switch (delayForUnit) {
|
switch (delayForUnit) {
|
||||||
case 'minutes':
|
case 'minutes':
|
||||||
return delayForValue * 60 * 1000;
|
return delayForValue * 60 * 1000;
|
@@ -1,4 +1,4 @@
|
|||||||
const delayUntilAsMilliseconds = (delayUntil: string) => {
|
const delayUntilAsMilliseconds = (delayUntil) => {
|
||||||
const delayUntilDate = new Date(delayUntil);
|
const delayUntilDate = new Date(delayUntil);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
@@ -1,22 +1,17 @@
|
|||||||
import SamlAuthProvider from '../models/saml-auth-provider.ee';
|
|
||||||
import User from '../models/user';
|
import User from '../models/user';
|
||||||
import Identity from '../models/identity.ee';
|
import Identity from '../models/identity.ee';
|
||||||
import SamlAuthProvidersRoleMapping from '../models/saml-auth-providers-role-mapping.ee';
|
|
||||||
|
|
||||||
const getUser = (
|
const getUser = (user, providerConfig) => ({
|
||||||
user: Record<string, unknown>,
|
|
||||||
providerConfig: SamlAuthProvider
|
|
||||||
) => ({
|
|
||||||
name: user[providerConfig.firstnameAttributeName],
|
name: user[providerConfig.firstnameAttributeName],
|
||||||
surname: user[providerConfig.surnameAttributeName],
|
surname: user[providerConfig.surnameAttributeName],
|
||||||
id: user.nameID,
|
id: user.nameID,
|
||||||
email: user[providerConfig.emailAttributeName],
|
email: user[providerConfig.emailAttributeName],
|
||||||
role: user[providerConfig.roleAttributeName] as string | string[],
|
role: user[providerConfig.roleAttributeName],
|
||||||
});
|
});
|
||||||
|
|
||||||
const findOrCreateUserBySamlIdentity = async (
|
const findOrCreateUserBySamlIdentity = async (
|
||||||
userIdentity: Record<string, unknown>,
|
userIdentity,
|
||||||
samlAuthProvider: SamlAuthProvider
|
samlAuthProvider
|
||||||
) => {
|
) => {
|
||||||
const mappedUser = getUser(userIdentity, samlAuthProvider);
|
const mappedUser = getUser(userIdentity, samlAuthProvider);
|
||||||
const identity = await Identity.query().findOne({
|
const identity = await Identity.query().findOne({
|
||||||
@@ -46,12 +41,12 @@ const findOrCreateUserBySamlIdentity = async (
|
|||||||
fullName: [mappedUser.name, mappedUser.surname]
|
fullName: [mappedUser.name, mappedUser.surname]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' '),
|
.join(' '),
|
||||||
email: mappedUser.email as string,
|
email: mappedUser.email,
|
||||||
roleId:
|
roleId:
|
||||||
samlAuthProviderRoleMapping?.roleId || samlAuthProvider.defaultRoleId,
|
samlAuthProviderRoleMapping?.roleId || samlAuthProvider.defaultRoleId,
|
||||||
identities: [
|
identities: [
|
||||||
{
|
{
|
||||||
remoteId: mappedUser.id as string,
|
remoteId: mappedUser.id,
|
||||||
providerId: samlAuthProvider.id,
|
providerId: samlAuthProvider.id,
|
||||||
providerType: 'saml',
|
providerType: 'saml',
|
||||||
},
|
},
|
@@ -1,17 +1,9 @@
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import {
|
|
||||||
IAction,
|
|
||||||
IApp,
|
|
||||||
IRawAction,
|
|
||||||
IRawTrigger,
|
|
||||||
ITrigger,
|
|
||||||
} from '@automatisch/types';
|
|
||||||
import { omit, cloneDeep } from 'lodash';
|
import { omit, cloneDeep } from 'lodash';
|
||||||
import addAuthenticationSteps from './add-authentication-steps';
|
import addAuthenticationSteps from './add-authentication-steps';
|
||||||
import addReconnectionSteps from './add-reconnection-steps';
|
import addReconnectionSteps from './add-reconnection-steps';
|
||||||
|
|
||||||
type TApps = Record<string, Promise<{ default: IApp }>>;
|
|
||||||
const apps = fs
|
const apps = fs
|
||||||
.readdirSync(path.resolve(__dirname, `../apps/`), { withFileTypes: true })
|
.readdirSync(path.resolve(__dirname, `../apps/`), { withFileTypes: true })
|
||||||
.reduce((apps, dirent) => {
|
.reduce((apps, dirent) => {
|
||||||
@@ -20,33 +12,35 @@ const apps = fs
|
|||||||
apps[dirent.name] = import(path.resolve(__dirname, '../apps', dirent.name));
|
apps[dirent.name] = import(path.resolve(__dirname, '../apps', dirent.name));
|
||||||
|
|
||||||
return apps;
|
return apps;
|
||||||
}, {} as TApps);
|
}, {});
|
||||||
|
|
||||||
async function getAppDefaultExport(appKey: string) {
|
async function getAppDefaultExport(appKey) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(apps, appKey)) {
|
if (!Object.prototype.hasOwnProperty.call(apps, appKey)) {
|
||||||
throw new Error(`An application with the "${appKey}" key couldn't be found.`);
|
throw new Error(
|
||||||
|
`An application with the "${appKey}" key couldn't be found.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await apps[appKey]).default;
|
return (await apps[appKey]).default;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripFunctions<C>(data: C): C {
|
function stripFunctions(data) {
|
||||||
return JSON.parse(JSON.stringify(data));
|
return JSON.parse(JSON.stringify(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
const getApp = async (appKey: string, stripFuncs = true) => {
|
const getApp = async (appKey, stripFuncs = true) => {
|
||||||
let appData: IApp = cloneDeep(await getAppDefaultExport(appKey));
|
let appData = cloneDeep(await getAppDefaultExport(appKey));
|
||||||
|
|
||||||
if (appData.auth) {
|
if (appData.auth) {
|
||||||
appData = addAuthenticationSteps(appData);
|
appData = addAuthenticationSteps(appData);
|
||||||
appData = addReconnectionSteps(appData);
|
appData = addReconnectionSteps(appData);
|
||||||
}
|
}
|
||||||
|
|
||||||
appData.triggers = appData?.triggers?.map((trigger: IRawTrigger) => {
|
appData.triggers = appData?.triggers?.map((trigger) => {
|
||||||
return addStaticSubsteps('trigger', appData, trigger);
|
return addStaticSubsteps('trigger', appData, trigger);
|
||||||
});
|
});
|
||||||
|
|
||||||
appData.actions = appData?.actions?.map((action: IRawAction) => {
|
appData.actions = appData?.actions?.map((action) => {
|
||||||
return addStaticSubsteps('action', appData, action);
|
return addStaticSubsteps('action', appData, action);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -62,19 +56,15 @@ const chooseConnectionStep = {
|
|||||||
name: 'Choose connection',
|
name: 'Choose connection',
|
||||||
};
|
};
|
||||||
|
|
||||||
const testStep = (stepType: 'trigger' | 'action') => {
|
const testStep = (stepType) => {
|
||||||
return {
|
return {
|
||||||
key: 'testStep',
|
key: 'testStep',
|
||||||
name: stepType === 'trigger' ? 'Test trigger' : 'Test action',
|
name: stepType === 'trigger' ? 'Test trigger' : 'Test action',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const addStaticSubsteps = (
|
const addStaticSubsteps = (stepType, appData, step) => {
|
||||||
stepType: 'trigger' | 'action',
|
const computedStep = omit(step, ['arguments']);
|
||||||
appData: IApp,
|
|
||||||
step: IRawTrigger | IRawAction
|
|
||||||
) => {
|
|
||||||
const computedStep: ITrigger | IAction = omit(step, ['arguments']);
|
|
||||||
|
|
||||||
computedStep.substeps = [];
|
computedStep.substeps = [];
|
||||||
|
|
@@ -1,32 +1,8 @@
|
|||||||
import createHttpClient from './http-client';
|
import createHttpClient from './http-client';
|
||||||
import Connection from '../models/connection';
|
|
||||||
import Flow from '../models/flow';
|
|
||||||
import Step from '../models/step';
|
|
||||||
import Execution from '../models/execution';
|
|
||||||
import {
|
|
||||||
IJSONObject,
|
|
||||||
IApp,
|
|
||||||
IGlobalVariable,
|
|
||||||
ITriggerItem,
|
|
||||||
IActionItem,
|
|
||||||
IRequest,
|
|
||||||
} from '@automatisch/types';
|
|
||||||
import EarlyExitError from '../errors/early-exit';
|
import EarlyExitError from '../errors/early-exit';
|
||||||
import AlreadyProcessedError from '../errors/already-processed';
|
import AlreadyProcessedError from '../errors/already-processed';
|
||||||
|
|
||||||
type GlobalVariableOptions = {
|
const globalVariable = async (options) => {
|
||||||
connection?: Connection;
|
|
||||||
app?: IApp;
|
|
||||||
flow?: Flow;
|
|
||||||
step?: Step;
|
|
||||||
execution?: Execution;
|
|
||||||
testRun?: boolean;
|
|
||||||
request?: IRequest;
|
|
||||||
};
|
|
||||||
|
|
||||||
const globalVariable = async (
|
|
||||||
options: GlobalVariableOptions
|
|
||||||
): Promise<IGlobalVariable> => {
|
|
||||||
const {
|
const {
|
||||||
connection,
|
connection,
|
||||||
app,
|
app,
|
||||||
@@ -41,9 +17,9 @@ const globalVariable = async (
|
|||||||
const lastInternalId = testRun ? undefined : await flow?.lastInternalId();
|
const lastInternalId = testRun ? undefined : await flow?.lastInternalId();
|
||||||
const nextStep = await step?.getNextStep();
|
const nextStep = await step?.getNextStep();
|
||||||
|
|
||||||
const $: IGlobalVariable = {
|
const $ = {
|
||||||
auth: {
|
auth: {
|
||||||
set: async (args: IJSONObject) => {
|
set: async (args) => {
|
||||||
if (connection) {
|
if (connection) {
|
||||||
await connection.$query().patchAndFetch({
|
await connection.$query().patchAndFetch({
|
||||||
formattedData: {
|
formattedData: {
|
||||||
@@ -91,7 +67,7 @@ const globalVariable = async (
|
|||||||
raw: null,
|
raw: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pushTriggerItem: (triggerItem: ITriggerItem) => {
|
pushTriggerItem: (triggerItem) => {
|
||||||
if (
|
if (
|
||||||
isAlreadyProcessed(triggerItem.meta.internalId) &&
|
isAlreadyProcessed(triggerItem.meta.internalId) &&
|
||||||
!$.execution.testRun
|
!$.execution.testRun
|
||||||
@@ -109,7 +85,7 @@ const globalVariable = async (
|
|||||||
throw new EarlyExitError();
|
throw new EarlyExitError();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setActionItem: (actionItem: IActionItem) => {
|
setActionItem: (actionItem) => {
|
||||||
$.actionOutput.data = actionItem;
|
$.actionOutput.data = actionItem;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -151,7 +127,7 @@ const globalVariable = async (
|
|||||||
? []
|
? []
|
||||||
: await flow?.lastInternalIds(2000);
|
: await flow?.lastInternalIds(2000);
|
||||||
|
|
||||||
const isAlreadyProcessed = (internalId: string) => {
|
const isAlreadyProcessed = (internalId) => {
|
||||||
return lastInternalIds?.includes(internalId);
|
return lastInternalIds?.includes(internalId);
|
||||||
};
|
};
|
||||||
|
|
@@ -6,9 +6,9 @@ import { addResolversToSchema } from '@graphql-tools/schema';
|
|||||||
import { applyMiddleware } from 'graphql-middleware';
|
import { applyMiddleware } from 'graphql-middleware';
|
||||||
|
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
import logger from '../helpers/logger';
|
import logger from './logger';
|
||||||
import authentication from '../helpers/authentication';
|
import authentication from './authentication';
|
||||||
import * as Sentry from '../helpers/sentry.ee';
|
import * as Sentry from './sentry.ee';
|
||||||
import resolvers from '../graphql/resolvers';
|
import resolvers from '../graphql/resolvers';
|
||||||
import HttpError from '../errors/http';
|
import HttpError from '../errors/http';
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ const graphQLInstance = graphqlHTTP({
|
|||||||
logger.error(error.path + ' : ' + error.message + '\n' + error.stack);
|
logger.error(error.path + ' : ' + error.message + '\n' + error.stack);
|
||||||
|
|
||||||
if (error.originalError instanceof HttpError) {
|
if (error.originalError instanceof HttpError) {
|
||||||
delete (error.originalError as HttpError).response;
|
delete error.originalError.response;
|
||||||
}
|
}
|
||||||
|
|
||||||
Sentry.captureException(error, {
|
Sentry.captureException(error, {
|
||||||
@@ -36,9 +36,9 @@ const graphQLInstance = graphqlHTTP({
|
|||||||
extra: {
|
extra: {
|
||||||
source: error.source?.body,
|
source: error.source?.body,
|
||||||
positions: error.positions,
|
positions: error.positions,
|
||||||
path: error.path
|
path: error.path,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
},
|
},
|
@@ -1,14 +1,10 @@
|
|||||||
import { IHttpClientParams } from '@automatisch/types';
|
|
||||||
import { InternalAxiosRequestConfig } from 'axios';
|
|
||||||
import { URL } from 'node:url';
|
import { URL } from 'node:url';
|
||||||
export { AxiosInstance as IHttpClient } from 'axios';
|
export { AxiosInstance as IHttpClient } from 'axios';
|
||||||
|
|
||||||
import HttpError from '../../errors/http';
|
import HttpError from '../../errors/http';
|
||||||
import axios from '../axios-with-proxy';
|
import axios from '../axios-with-proxy';
|
||||||
|
|
||||||
const removeBaseUrlForAbsoluteUrls = (
|
const removeBaseUrlForAbsoluteUrls = (requestConfig) => {
|
||||||
requestConfig: InternalAxiosRequestConfig
|
|
||||||
): InternalAxiosRequestConfig => {
|
|
||||||
try {
|
try {
|
||||||
const url = new URL(requestConfig.url);
|
const url = new URL(requestConfig.url);
|
||||||
requestConfig.baseURL = url.origin;
|
requestConfig.baseURL = url.origin;
|
||||||
@@ -20,33 +16,27 @@ const removeBaseUrlForAbsoluteUrls = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function createHttpClient({
|
export default function createHttpClient({ $, baseURL, beforeRequest = [] }) {
|
||||||
$,
|
|
||||||
baseURL,
|
|
||||||
beforeRequest = [],
|
|
||||||
}: IHttpClientParams) {
|
|
||||||
const instance = axios.create({
|
const instance = axios.create({
|
||||||
baseURL,
|
baseURL,
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.interceptors.request.use(
|
instance.interceptors.request.use((requestConfig) => {
|
||||||
(requestConfig: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
|
const newRequestConfig = removeBaseUrlForAbsoluteUrls(requestConfig);
|
||||||
const newRequestConfig = removeBaseUrlForAbsoluteUrls(requestConfig);
|
|
||||||
|
|
||||||
const result = beforeRequest.reduce((newConfig, beforeRequestFunc) => {
|
const result = beforeRequest.reduce((newConfig, beforeRequestFunc) => {
|
||||||
return beforeRequestFunc($, newConfig);
|
return beforeRequestFunc($, newConfig);
|
||||||
}, newRequestConfig);
|
}, newRequestConfig);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* axios seems to want InternalAxiosRequestConfig returned not AxioRequestConfig
|
* axios seems to want InternalAxiosRequestConfig returned not AxioRequestConfig
|
||||||
* anymore even though requests do require AxiosRequestConfig.
|
* anymore even though requests do require AxiosRequestConfig.
|
||||||
*
|
*
|
||||||
* Since both interfaces are very similar (InternalAxiosRequestConfig
|
* Since both interfaces are very similar (InternalAxiosRequestConfig
|
||||||
* extends AxiosRequestConfig), we can utilize an assertion below
|
* extends AxiosRequestConfig), we can utilize an assertion below
|
||||||
**/
|
**/
|
||||||
return result as InternalAxiosRequestConfig;
|
return result;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
instance.interceptors.response.use(
|
instance.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
@@ -1,12 +1,7 @@
|
|||||||
import { Application } from 'express';
|
|
||||||
import { ExpressAdapter } from '@bull-board/express';
|
|
||||||
import basicAuth from 'express-basic-auth';
|
import basicAuth from 'express-basic-auth';
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
|
|
||||||
const injectBullBoardHandler = async (
|
const injectBullBoardHandler = async (app, serverAdapter) => {
|
||||||
app: Application,
|
|
||||||
serverAdapter: ExpressAdapter
|
|
||||||
) => {
|
|
||||||
if (
|
if (
|
||||||
!appConfig.enableBullMQDashboard ||
|
!appConfig.enableBullMQDashboard ||
|
||||||
!appConfig.bullMQDashboardUsername ||
|
!appConfig.bullMQDashboardUsername ||
|
@@ -1,14 +1,13 @@
|
|||||||
import morgan, { StreamOptions } from 'morgan';
|
import morgan from 'morgan';
|
||||||
import { Request } from 'express';
|
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
|
||||||
const stream: StreamOptions = {
|
const stream = {
|
||||||
write: (message) =>
|
write: (message) =>
|
||||||
logger.http(message.substring(0, message.lastIndexOf('\n'))),
|
logger.http(message.substring(0, message.lastIndexOf('\n'))),
|
||||||
};
|
};
|
||||||
|
|
||||||
const registerGraphQLToken = () => {
|
const registerGraphQLToken = () => {
|
||||||
morgan.token('graphql-query', (req: Request) => {
|
morgan.token('graphql-query', (req) => {
|
||||||
if (req.body.query) {
|
if (req.body.query) {
|
||||||
return `GraphQL ${req.body.query}`;
|
return `GraphQL ${req.body.query}`;
|
||||||
}
|
}
|
@@ -1,12 +1,4 @@
|
|||||||
import { Model } from 'objection';
|
const paginate = async (query, limit, offset) => {
|
||||||
import ExtendedQueryBuilder from '../models/query-builder';
|
|
||||||
import type Base from '../models/base';
|
|
||||||
|
|
||||||
const paginate = async (
|
|
||||||
query: ExtendedQueryBuilder<Model, Model[]>,
|
|
||||||
limit: 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');
|
||||||
}
|
}
|
||||||
@@ -22,7 +14,7 @@ const paginate = async (
|
|||||||
totalPages: Math.ceil(count / limit),
|
totalPages: Math.ceil(count / limit),
|
||||||
},
|
},
|
||||||
totalCount: count,
|
totalCount: count,
|
||||||
edges: records.map((record: Base) => ({
|
edges: records.map((record) => ({
|
||||||
node: record,
|
node: record,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
@@ -1,38 +1,16 @@
|
|||||||
type TParameters = {
|
export default function parseLinkHeader(link) {
|
||||||
[key: string]: string;
|
const parsed = {};
|
||||||
rel?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TReference = {
|
|
||||||
uri: string;
|
|
||||||
parameters: TParameters;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TRel = 'next' | 'prev' | 'first' | 'last';
|
|
||||||
|
|
||||||
type TParsedLinkHeader = {
|
|
||||||
next?: TReference;
|
|
||||||
prev?: TReference;
|
|
||||||
first?: TReference;
|
|
||||||
last?: TReference;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function parseLinkHeader(link: string): TParsedLinkHeader {
|
|
||||||
const parsed: TParsedLinkHeader = {};
|
|
||||||
|
|
||||||
if (!link) return parsed;
|
if (!link) return parsed;
|
||||||
|
|
||||||
const items = link.split(',');
|
const items = link.split(',');
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const [rawUriReference, ...rawLinkParameters] = item.split(';') as [
|
const [rawUriReference, ...rawLinkParameters] = item.split(';');
|
||||||
string,
|
|
||||||
...string[]
|
|
||||||
];
|
|
||||||
const trimmedUriReference = rawUriReference.trim();
|
const trimmedUriReference = rawUriReference.trim();
|
||||||
|
|
||||||
const reference = trimmedUriReference.slice(1, -1);
|
const reference = trimmedUriReference.slice(1, -1);
|
||||||
const parameters: TParameters = {};
|
const parameters = {};
|
||||||
|
|
||||||
for (const rawParameter of rawLinkParameters) {
|
for (const rawParameter of rawLinkParameters) {
|
||||||
const trimmedRawParameter = rawParameter.trim();
|
const trimmedRawParameter = rawParameter.trim();
|
||||||
@@ -41,7 +19,7 @@ export default function parseLinkHeader(link: string): TParsedLinkHeader {
|
|||||||
parameters[key.trim()] = value.slice(1, -1);
|
parameters[key.trim()] = value.slice(1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed[parameters.rel as TRel] = {
|
parsed[parameters.rel] = {
|
||||||
uri: reference,
|
uri: reference,
|
||||||
parameters,
|
parameters,
|
||||||
};
|
};
|
89
packages/backend/src/helpers/passport.js
Normal file
89
packages/backend/src/helpers/passport.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { URL } from 'node:url';
|
||||||
|
import { MultiSamlStrategy } from '@node-saml/passport-saml';
|
||||||
|
import passport from 'passport';
|
||||||
|
|
||||||
|
import appConfig from '../config/app';
|
||||||
|
import createAuthTokenByUserId from './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) {
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!authProvider) {
|
||||||
|
return done(notFoundIssuer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return done(null, authProvider.config);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async function (request, user, 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,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!authProvider) {
|
||||||
|
return done(notFoundIssuer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundUserWithIdentity = await findOrCreateUserBySamlIdentity(
|
||||||
|
user,
|
||||||
|
authProvider
|
||||||
|
);
|
||||||
|
return done(null, foundUserWithIdentity);
|
||||||
|
},
|
||||||
|
function (request, user, done) {
|
||||||
|
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, res) => {
|
||||||
|
const token = createAuthTokenByUserId(req.currentUser.id);
|
||||||
|
|
||||||
|
const redirectUrl = new URL(
|
||||||
|
`/login/callback?token=${token}`,
|
||||||
|
appConfig.webAppUrl
|
||||||
|
).toString();
|
||||||
|
res.redirect(redirectUrl);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
@@ -1,84 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
@@ -1,13 +1,11 @@
|
|||||||
import { Express } from 'express';
|
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import type { CaptureContext } from '@sentry/types';
|
|
||||||
import * as Tracing from '@sentry/tracing';
|
import * as Tracing from '@sentry/tracing';
|
||||||
|
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
|
|
||||||
const isSentryEnabled = !!appConfig.sentryDsn;
|
const isSentryEnabled = !!appConfig.sentryDsn;
|
||||||
|
|
||||||
export function init(app?: Express) {
|
export function init(app) {
|
||||||
if (!isSentryEnabled) return;
|
if (!isSentryEnabled) return;
|
||||||
|
|
||||||
return Sentry.init({
|
return Sentry.init({
|
||||||
@@ -22,31 +20,32 @@ export function init(app?: Express) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function attachRequestHandler(app) {
|
||||||
export function attachRequestHandler(app: Express) {
|
|
||||||
if (!isSentryEnabled) return;
|
if (!isSentryEnabled) return;
|
||||||
|
|
||||||
app.use(Sentry.Handlers.requestHandler());
|
app.use(Sentry.Handlers.requestHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function attachTracingHandler(app: Express) {
|
export function attachTracingHandler(app) {
|
||||||
if (!isSentryEnabled) return;
|
if (!isSentryEnabled) return;
|
||||||
|
|
||||||
app.use(Sentry.Handlers.tracingHandler());
|
app.use(Sentry.Handlers.tracingHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function attachErrorHandler(app: Express) {
|
export function attachErrorHandler(app) {
|
||||||
if (!isSentryEnabled) return;
|
if (!isSentryEnabled) return;
|
||||||
|
|
||||||
app.use(Sentry.Handlers.errorHandler({
|
app.use(
|
||||||
shouldHandleError() {
|
Sentry.Handlers.errorHandler({
|
||||||
// TODO: narrow down the captured errors in time as we receive samples
|
shouldHandleError() {
|
||||||
return true;
|
// TODO: narrow down the captured errors in time as we receive samples
|
||||||
}
|
return true;
|
||||||
}));
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function captureException(exception: any, captureContext?: CaptureContext) {
|
export function captureException(exception, captureContext) {
|
||||||
if (!isSentryEnabled) return;
|
if (!isSentryEnabled) return;
|
||||||
|
|
||||||
return Sentry.captureException(exception, captureContext);
|
return Sentry.captureException(exception, captureContext);
|
@@ -1,12 +1,7 @@
|
|||||||
import Analytics, { apiObject } from '@rudderstack/rudder-sdk-node';
|
import Analytics from '@rudderstack/rudder-sdk-node';
|
||||||
import organizationId from './organization-id';
|
import organizationId from './organization-id';
|
||||||
import instanceId from './instance-id';
|
import instanceId from './instance-id';
|
||||||
import appConfig from '../../config/app';
|
import appConfig from '../../config/app';
|
||||||
import Step from '../../models/step';
|
|
||||||
import Flow from '../../models/flow';
|
|
||||||
import Execution from '../../models/execution';
|
|
||||||
import ExecutionStep from '../../models/execution-step';
|
|
||||||
import Connection from '../../models/connection';
|
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|
||||||
const WRITE_KEY = '284Py4VgK2MsNYV7xlKzyrALx0v';
|
const WRITE_KEY = '284Py4VgK2MsNYV7xlKzyrALx0v';
|
||||||
@@ -15,22 +10,17 @@ const CPUS = os.cpus();
|
|||||||
const SIX_HOURS_IN_MILLISECONDS = 21600000;
|
const SIX_HOURS_IN_MILLISECONDS = 21600000;
|
||||||
|
|
||||||
class Telemetry {
|
class Telemetry {
|
||||||
organizationId: string;
|
|
||||||
instanceId: string;
|
|
||||||
client: Analytics;
|
|
||||||
serviceType: string;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.client = new Analytics(WRITE_KEY, DATA_PLANE_URL);
|
this.client = new Analytics(WRITE_KEY, DATA_PLANE_URL);
|
||||||
this.organizationId = organizationId();
|
this.organizationId = organizationId();
|
||||||
this.instanceId = instanceId();
|
this.instanceId = instanceId();
|
||||||
}
|
}
|
||||||
|
|
||||||
setServiceType(type: string) {
|
setServiceType(type) {
|
||||||
this.serviceType = type;
|
this.serviceType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
track(name: string, properties: apiObject) {
|
track(name, properties) {
|
||||||
if (!appConfig.telemetryEnabled) {
|
if (!appConfig.telemetryEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -48,7 +38,7 @@ class Telemetry {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
stepCreated(step: Step) {
|
stepCreated(step) {
|
||||||
this.track('stepCreated', {
|
this.track('stepCreated', {
|
||||||
stepId: step.id,
|
stepId: step.id,
|
||||||
flowId: step.flowId,
|
flowId: step.flowId,
|
||||||
@@ -57,7 +47,7 @@ class Telemetry {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
stepUpdated(step: Step) {
|
stepUpdated(step) {
|
||||||
this.track('stepUpdated', {
|
this.track('stepUpdated', {
|
||||||
stepId: step.id,
|
stepId: step.id,
|
||||||
flowId: step.flowId,
|
flowId: step.flowId,
|
||||||
@@ -71,7 +61,7 @@ class Telemetry {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
flowCreated(flow: Flow) {
|
flowCreated(flow) {
|
||||||
this.track('flowCreated', {
|
this.track('flowCreated', {
|
||||||
flowId: flow.id,
|
flowId: flow.id,
|
||||||
name: flow.name,
|
name: flow.name,
|
||||||
@@ -81,7 +71,7 @@ class Telemetry {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
flowUpdated(flow: Flow) {
|
flowUpdated(flow) {
|
||||||
this.track('flowUpdated', {
|
this.track('flowUpdated', {
|
||||||
flowId: flow.id,
|
flowId: flow.id,
|
||||||
name: flow.name,
|
name: flow.name,
|
||||||
@@ -91,7 +81,7 @@ class Telemetry {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
executionCreated(execution: Execution) {
|
executionCreated(execution) {
|
||||||
this.track('executionCreated', {
|
this.track('executionCreated', {
|
||||||
executionId: execution.id,
|
executionId: execution.id,
|
||||||
flowId: execution.flowId,
|
flowId: execution.flowId,
|
||||||
@@ -101,7 +91,7 @@ class Telemetry {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
executionStepCreated(executionStep: ExecutionStep) {
|
executionStepCreated(executionStep) {
|
||||||
this.track('executionStepCreated', {
|
this.track('executionStepCreated', {
|
||||||
executionStepId: executionStep.id,
|
executionStepId: executionStep.id,
|
||||||
executionId: executionStep.executionId,
|
executionId: executionStep.executionId,
|
||||||
@@ -112,7 +102,7 @@ class Telemetry {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionCreated(connection: Connection) {
|
connectionCreated(connection) {
|
||||||
this.track('connectionCreated', {
|
this.track('connectionCreated', {
|
||||||
connectionId: connection.id,
|
connectionId: connection.id,
|
||||||
key: connection.key,
|
key: connection.key,
|
||||||
@@ -122,7 +112,7 @@ class Telemetry {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionUpdated(connection: Connection) {
|
connectionUpdated(connection) {
|
||||||
this.track('connectionUpdated', {
|
this.track('connectionUpdated', {
|
||||||
connectionId: connection.id,
|
connectionId: connection.id,
|
||||||
key: connection.key,
|
key: connection.key,
|
@@ -1,20 +1,23 @@
|
|||||||
import { PureAbility, fieldPatternMatcher, mongoQueryMatcher } from '@casl/ability';
|
import {
|
||||||
import type User from '../models/user'
|
PureAbility,
|
||||||
|
fieldPatternMatcher,
|
||||||
|
mongoQueryMatcher,
|
||||||
|
} from '@casl/ability';
|
||||||
|
|
||||||
// Must be kept in sync with `packages/web/src/helpers/userAbility.ts`!
|
// Must be kept in sync with `packages/web/src/helpers/userAbility.ts`!
|
||||||
export default function userAbility(user: Partial<User>) {
|
export default function userAbility(user) {
|
||||||
const permissions = user?.permissions;
|
const permissions = user?.permissions;
|
||||||
const role = user?.role;
|
const role = user?.role;
|
||||||
|
|
||||||
// We're not using mongo, but our fields, conditions match
|
// We're not using mongo, but our fields, conditions match
|
||||||
const options = {
|
const options = {
|
||||||
conditionsMatcher: mongoQueryMatcher,
|
conditionsMatcher: mongoQueryMatcher,
|
||||||
fieldMatcher: fieldPatternMatcher
|
fieldMatcher: fieldPatternMatcher,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!role || !permissions) {
|
if (!role || !permissions) {
|
||||||
return new PureAbility([], options);
|
return new PureAbility([], options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PureAbility<[string, string], string[]>(permissions, options);
|
return new PureAbility(permissions, options);
|
||||||
}
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
import express, { Application } from 'express';
|
import express from 'express';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
|
|
||||||
const webUIHandler = async (app: Application) => {
|
const webUIHandler = async (app) => {
|
||||||
if (appConfig.serveWebAppSeparately) return;
|
if (appConfig.serveWebAppSeparately) return;
|
||||||
|
|
||||||
const webAppPath = require.resolve('@automatisch/web');
|
const webAppPath = require.resolve('@automatisch/web');
|
@@ -1,5 +1,3 @@
|
|||||||
import { Response } from 'express';
|
|
||||||
import { IRequest } from '@automatisch/types';
|
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
import Flow from '../models/flow';
|
import Flow from '../models/flow';
|
||||||
@@ -12,11 +10,7 @@ import {
|
|||||||
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
||||||
} from './remove-job-configuration';
|
} from './remove-job-configuration';
|
||||||
|
|
||||||
export default async (
|
export default async (flowId, request, response) => {
|
||||||
flowId: string,
|
|
||||||
request: IRequest,
|
|
||||||
response: Response
|
|
||||||
) => {
|
|
||||||
const flow = await Flow.query().findById(flowId).throwIfNotFound();
|
const flow = await Flow.query().findById(flowId).throwIfNotFound();
|
||||||
const user = await flow.$relatedQuery('user');
|
const user = await flow.$relatedQuery('user');
|
||||||
|
|
Reference in New Issue
Block a user