feat: Convert helpers to use JS files

This commit is contained in:
Faruk AYDIN
2024-01-04 19:55:41 +01:00
parent 8819ddefa7
commit 85141812d9
48 changed files with 259 additions and 384 deletions

View File

@@ -1,6 +1,4 @@
import { IApp } from '@automatisch/types';
function addAuthenticationSteps(app: IApp): IApp {
function addAuthenticationSteps(app) {
if (app.auth.generateAuthUrl) {
app.auth.authenticationSteps = authenticationStepsWithAuthUrl;
app.auth.sharedAuthenticationSteps = sharedAuthenticationStepsWithAuthUrl;
@@ -13,7 +11,7 @@ function addAuthenticationSteps(app: IApp): IApp {
const authenticationStepsWithoutAuthUrl = [
{
type: 'mutation' as const,
type: 'mutation',
name: 'createConnection',
arguments: [
{
@@ -27,7 +25,7 @@ const authenticationStepsWithoutAuthUrl = [
],
},
{
type: 'mutation' as const,
type: 'mutation',
name: 'verifyConnection',
arguments: [
{
@@ -40,7 +38,7 @@ const authenticationStepsWithoutAuthUrl = [
const authenticationStepsWithAuthUrl = [
{
type: 'mutation' as const,
type: 'mutation',
name: 'createConnection',
arguments: [
{
@@ -54,7 +52,7 @@ const authenticationStepsWithAuthUrl = [
],
},
{
type: 'mutation' as const,
type: 'mutation',
name: 'generateAuthUrl',
arguments: [
{
@@ -64,7 +62,7 @@ const authenticationStepsWithAuthUrl = [
],
},
{
type: 'openWithPopup' as const,
type: 'openWithPopup',
name: 'openAuthPopup',
arguments: [
{
@@ -74,7 +72,7 @@ const authenticationStepsWithAuthUrl = [
],
},
{
type: 'mutation' as const,
type: 'mutation',
name: 'updateConnection',
arguments: [
{
@@ -88,7 +86,7 @@ const authenticationStepsWithAuthUrl = [
],
},
{
type: 'mutation' as const,
type: 'mutation',
name: 'verifyConnection',
arguments: [
{
@@ -101,7 +99,7 @@ const authenticationStepsWithAuthUrl = [
const sharedAuthenticationStepsWithAuthUrl = [
{
type: 'mutation' as const,
type: 'mutation',
name: 'createConnection',
arguments: [
{
@@ -115,7 +113,7 @@ const sharedAuthenticationStepsWithAuthUrl = [
],
},
{
type: 'mutation' as const,
type: 'mutation',
name: 'generateAuthUrl',
arguments: [
{
@@ -125,7 +123,7 @@ const sharedAuthenticationStepsWithAuthUrl = [
],
},
{
type: 'openWithPopup' as const,
type: 'openWithPopup',
name: 'openAuthPopup',
arguments: [
{
@@ -135,7 +133,7 @@ const sharedAuthenticationStepsWithAuthUrl = [
],
},
{
type: 'mutation' as const,
type: 'mutation',
name: 'updateConnection',
arguments: [
{
@@ -149,7 +147,7 @@ const sharedAuthenticationStepsWithAuthUrl = [
],
},
{
type: 'mutation' as const,
type: 'mutation',
name: 'verifyConnection',
arguments: [
{

View File

@@ -1,8 +1,3 @@
import {
IApp,
IAuthenticationStep,
IAuthenticationStepField,
} from '@automatisch/types';
import cloneDeep from 'lodash/cloneDeep';
const connectionIdArgument = {
@@ -11,20 +6,20 @@ const connectionIdArgument = {
};
const resetConnectionStep = {
type: 'mutation' as const,
type: 'mutation',
name: 'resetConnection',
arguments: [connectionIdArgument],
};
function replaceCreateConnection(string: string) {
function replaceCreateConnection(string) {
return string.replace('{createConnection.id}', '{connection.id}');
}
function removeAppKeyArgument(args: IAuthenticationStepField[]) {
function removeAppKeyArgument(args) {
return args.filter((argument) => argument.name !== 'key');
}
function addConnectionId(step: IAuthenticationStep) {
function addConnectionId(step) {
step.arguments = step.arguments.map((argument) => {
if (typeof argument.value === 'string') {
argument.value = replaceCreateConnection(argument.value);
@@ -45,7 +40,7 @@ function addConnectionId(step: IAuthenticationStep) {
return step;
}
function replaceCreateConnectionsWithUpdate(steps: IAuthenticationStep[]) {
function replaceCreateConnectionsWithUpdate(steps) {
const updatedSteps = cloneDeep(steps);
return updatedSteps.map((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;
if (hasReconnectionSteps) return app;
@@ -80,7 +75,10 @@ function addReconnectionSteps(app: IApp): IApp {
app.auth.sharedAuthenticationSteps
);
app.auth.sharedReconnectionSteps = [resetConnectionStep, ...updatedStepsWithEmbeddedDefaults];
app.auth.sharedReconnectionSteps = [
resetConnectionStep,
...updatedStepsWithEmbeddedDefaults,
];
}
return app;

View File

@@ -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) => {
const { appKey } = req.params;
const svgPath = `${__dirname}/../apps/${appKey}/assets/favicon.svg`;

View File

@@ -1,7 +1,6 @@
import type { IApp } from '@automatisch/types';
import appConfig from '../config/app';
const appInfoConverter = (rawAppData: IApp) => {
const appInfoConverter = (rawAppData) => {
rawAppData.iconUrl = rawAppData.iconUrl.replace(
'{BASE_URL}',
appConfig.baseUrl

View File

@@ -9,9 +9,8 @@ const isAuthenticated = rule()(async (_parent, _args, req) => {
if (token == null) return false;
try {
const { userId } = jwt.verify(token, appConfig.appSecretKey) as {
userId: string;
};
const { userId } = jwt.verify(token, appConfig.appSecretKey);
req.currentUser = await User.query()
.findById(userId)
.leftJoinRelated({

View File

@@ -1,8 +1,8 @@
import axios, { AxiosRequestConfig } from 'axios';
import axios from 'axios';
import { HttpsProxyAgent } from 'https-proxy-agent';
import { HttpProxyAgent } from 'http-proxy-agent';
const config: AxiosRequestConfig = {};
const config = {};
const httpProxyUrl = process.env.http_proxy;
const httpsProxyUrl = process.env.https_proxy;
const supportsProxy = httpProxyUrl || httpsProxyUrl;

View File

@@ -9,7 +9,7 @@ const PADDLE_VENDOR_URL = appConfig.isDev
const axiosInstance = axios.create({ baseURL: PADDLE_VENDOR_URL });
const getSubscription = async (subscriptionId: number) => {
const getSubscription = async (subscriptionId) => {
const data = {
vendor_id: appConfig.paddleVendorId,
vendor_auth_code: appConfig.paddleVendorAuthCode,
@@ -24,7 +24,7 @@ const getSubscription = async (subscriptionId: number) => {
return subscription;
};
const getInvoices = async (subscriptionId: number) => {
const getInvoices = async (subscriptionId) => {
// TODO: iterate over previous subscriptions and include their invoices
const data = {
vendor_id: appConfig.paddleVendorId,

View File

@@ -22,7 +22,7 @@ const prodPlans = [
const plans = appConfig.isProd ? prodPlans : testPlans;
export function getPlanById(id: string) {
export function getPlanById(id) {
return plans.find((plan) => plan.productId === id);
}

View File

@@ -1,8 +1,7 @@
import { IRequest } from '@automatisch/types';
import Subscription from '../../models/subscription.ee';
import Billing from './index.ee';
const handleSubscriptionCreated = async (request: IRequest) => {
const handleSubscriptionCreated = async (request) => {
const subscription = await Subscription.query().insertAndFetch(
formatSubscription(request)
);
@@ -11,7 +10,7 @@ const handleSubscriptionCreated = async (request: IRequest) => {
.insert(formatUsageData(request));
};
const handleSubscriptionUpdated = async (request: IRequest) => {
const handleSubscriptionUpdated = async (request) => {
await Subscription.query()
.findOne({
paddle_subscription_id: request.body.subscription_id,
@@ -19,7 +18,7 @@ const handleSubscriptionUpdated = async (request: IRequest) => {
.patch(formatSubscription(request));
};
const handleSubscriptionCancelled = async (request: IRequest) => {
const handleSubscriptionCancelled = async (request) => {
const subscription = await Subscription.query().findOne({
paddle_subscription_id: request.body.subscription_id,
});
@@ -27,7 +26,7 @@ const handleSubscriptionCancelled = async (request: IRequest) => {
await subscription.$query().patchAndFetch(formatSubscription(request));
};
const handleSubscriptionPaymentSucceeded = async (request: IRequest) => {
const handleSubscriptionPaymentSucceeded = async (request) => {
const subscription = await Subscription.query()
.findOne({
paddle_subscription_id: request.body.subscription_id,
@@ -49,7 +48,7 @@ const handleSubscriptionPaymentSucceeded = async (request: IRequest) => {
.insert(formatUsageData(request));
};
const formatSubscription = (request: IRequest) => {
const formatSubscription = (request) => {
return {
userId: JSON.parse(request.body.passthrough).id,
paddleSubscriptionId: request.body.subscription_id,
@@ -63,7 +62,7 @@ const formatSubscription = (request: IRequest) => {
};
};
const formatUsageData = (request: IRequest) => {
const formatUsageData = (request) => {
return {
userId: JSON.parse(request.body.passthrough).id,
consumedTaskCount: 0,

View File

@@ -2,7 +2,7 @@ import * as path from 'path';
import * as fs from 'fs';
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 source = fs.readFileSync(filePath, 'utf-8').toString();
const template = handlebars.compile(source);

View File

@@ -1,23 +1,18 @@
import Step from '../models/step';
import ExecutionStep from '../models/execution-step';
import get from 'lodash.get';
const variableRegExp = /({{step\.[\da-zA-Z-]+(?:\.[^.}{]+)+}})/g;
export default function computeParameters(
parameters: Step['parameters'],
executionSteps: ExecutionStep[]
): Step['parameters'] {
export default function computeParameters(parameters, executionSteps) {
const entries = Object.entries(parameters);
return entries.reduce((result, [key, value]: [string, unknown]) => {
return entries.reduce((result, [key, value]) => {
if (typeof value === 'string') {
const parts = value.split(variableRegExp);
const computedValue = parts
.map((part: string) => {
.map((part) => {
const isVariable = part.match(variableRegExp);
if (isVariable) {
const stepIdAndKeyPath = part.replace(/{{step.|}}/g, '') as string;
const stepIdAndKeyPath = part.replace(/{{step.|}}/g, '');
const [stepId, ...keyPaths] = stepIdAndKeyPath.split('.');
const keyPath = keyPaths.join('.');
const executionStep = executionSteps.find((executionStep) => {

View File

@@ -3,7 +3,7 @@ import appConfig from '../config/app';
const TOKEN_EXPIRES_IN = '14d';
const createAuthTokenByUserId = (userId: string) => {
const createAuthTokenByUserId = (userId) => {
const token = jwt.sign({ userId }, appConfig.appSecretKey, {
expiresIn: TOKEN_EXPIRES_IN,
});

View File

@@ -31,7 +31,7 @@ const shouldEnableBullDashboard = () => {
);
};
const createBullBoardHandler = async (serverAdapter: ExpressAdapter) => {
const createBullBoardHandler = async (serverAdapter) => {
if (!shouldEnableBullDashboard) return;
createBullBoard({

View File

@@ -0,0 +1,3 @@
export default function defineAction(actionDefinition) {
return actionDefinition;
}

View File

@@ -1,5 +0,0 @@
import { IRawAction } from '@automatisch/types';
export default function defineAction(actionDefinition: IRawAction): IRawAction {
return actionDefinition;
}

View File

@@ -0,0 +1,3 @@
export default function defineApp(appDefinition) {
return appDefinition;
}

View File

@@ -1,5 +0,0 @@
import { IApp } from '@automatisch/types';
export default function defineApp(appDefinition: IApp): IApp {
return appDefinition;
}

View File

@@ -0,0 +1,3 @@
export default function defineTrigger(triggerDefinition) {
return triggerDefinition;
}

View File

@@ -1,7 +0,0 @@
import { IRawTrigger } from '@automatisch/types';
export default function defineTrigger(
triggerDefinition: IRawTrigger
): IRawTrigger {
return triggerDefinition;
}

View 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;

View File

@@ -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;

View File

@@ -1,9 +1,4 @@
export type TDelayForUnit = 'minutes' | 'hours' | 'days' | 'weeks';
const delayAsMilliseconds = (
delayForUnit: TDelayForUnit,
delayForValue: number
) => {
const delayAsMilliseconds = (delayForUnit, delayForValue) => {
switch (delayForUnit) {
case 'minutes':
return delayForValue * 60 * 1000;

View File

@@ -1,4 +1,4 @@
const delayUntilAsMilliseconds = (delayUntil: string) => {
const delayUntilAsMilliseconds = (delayUntil) => {
const delayUntilDate = new Date(delayUntil);
const now = new Date();

View File

@@ -1,22 +1,17 @@
import SamlAuthProvider from '../models/saml-auth-provider.ee';
import User from '../models/user';
import Identity from '../models/identity.ee';
import SamlAuthProvidersRoleMapping from '../models/saml-auth-providers-role-mapping.ee';
const getUser = (
user: Record<string, unknown>,
providerConfig: SamlAuthProvider
) => ({
const getUser = (user, providerConfig) => ({
name: user[providerConfig.firstnameAttributeName],
surname: user[providerConfig.surnameAttributeName],
id: user.nameID,
email: user[providerConfig.emailAttributeName],
role: user[providerConfig.roleAttributeName] as string | string[],
role: user[providerConfig.roleAttributeName],
});
const findOrCreateUserBySamlIdentity = async (
userIdentity: Record<string, unknown>,
samlAuthProvider: SamlAuthProvider
userIdentity,
samlAuthProvider
) => {
const mappedUser = getUser(userIdentity, samlAuthProvider);
const identity = await Identity.query().findOne({
@@ -46,12 +41,12 @@ const findOrCreateUserBySamlIdentity = async (
fullName: [mappedUser.name, mappedUser.surname]
.filter(Boolean)
.join(' '),
email: mappedUser.email as string,
email: mappedUser.email,
roleId:
samlAuthProviderRoleMapping?.roleId || samlAuthProvider.defaultRoleId,
identities: [
{
remoteId: mappedUser.id as string,
remoteId: mappedUser.id,
providerId: samlAuthProvider.id,
providerType: 'saml',
},

View File

@@ -1,17 +1,9 @@
import path from 'node:path';
import fs from 'node:fs';
import {
IAction,
IApp,
IRawAction,
IRawTrigger,
ITrigger,
} from '@automatisch/types';
import { omit, cloneDeep } from 'lodash';
import addAuthenticationSteps from './add-authentication-steps';
import addReconnectionSteps from './add-reconnection-steps';
type TApps = Record<string, Promise<{ default: IApp }>>;
const apps = fs
.readdirSync(path.resolve(__dirname, `../apps/`), { withFileTypes: true })
.reduce((apps, dirent) => {
@@ -20,33 +12,35 @@ const apps = fs
apps[dirent.name] = import(path.resolve(__dirname, '../apps', dirent.name));
return apps;
}, {} as TApps);
}, {});
async function getAppDefaultExport(appKey: string) {
async function getAppDefaultExport(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;
}
function stripFunctions<C>(data: C): C {
function stripFunctions(data) {
return JSON.parse(JSON.stringify(data));
}
const getApp = async (appKey: string, stripFuncs = true) => {
let appData: IApp = cloneDeep(await getAppDefaultExport(appKey));
const getApp = async (appKey, stripFuncs = true) => {
let appData = cloneDeep(await getAppDefaultExport(appKey));
if (appData.auth) {
appData = addAuthenticationSteps(appData);
appData = addReconnectionSteps(appData);
}
appData.triggers = appData?.triggers?.map((trigger: IRawTrigger) => {
appData.triggers = appData?.triggers?.map((trigger) => {
return addStaticSubsteps('trigger', appData, trigger);
});
appData.actions = appData?.actions?.map((action: IRawAction) => {
appData.actions = appData?.actions?.map((action) => {
return addStaticSubsteps('action', appData, action);
});
@@ -62,19 +56,15 @@ const chooseConnectionStep = {
name: 'Choose connection',
};
const testStep = (stepType: 'trigger' | 'action') => {
const testStep = (stepType) => {
return {
key: 'testStep',
name: stepType === 'trigger' ? 'Test trigger' : 'Test action',
};
};
const addStaticSubsteps = (
stepType: 'trigger' | 'action',
appData: IApp,
step: IRawTrigger | IRawAction
) => {
const computedStep: ITrigger | IAction = omit(step, ['arguments']);
const addStaticSubsteps = (stepType, appData, step) => {
const computedStep = omit(step, ['arguments']);
computedStep.substeps = [];

View File

@@ -1,32 +1,8 @@
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 AlreadyProcessedError from '../errors/already-processed';
type GlobalVariableOptions = {
connection?: Connection;
app?: IApp;
flow?: Flow;
step?: Step;
execution?: Execution;
testRun?: boolean;
request?: IRequest;
};
const globalVariable = async (
options: GlobalVariableOptions
): Promise<IGlobalVariable> => {
const globalVariable = async (options) => {
const {
connection,
app,
@@ -41,9 +17,9 @@ const globalVariable = async (
const lastInternalId = testRun ? undefined : await flow?.lastInternalId();
const nextStep = await step?.getNextStep();
const $: IGlobalVariable = {
const $ = {
auth: {
set: async (args: IJSONObject) => {
set: async (args) => {
if (connection) {
await connection.$query().patchAndFetch({
formattedData: {
@@ -91,7 +67,7 @@ const globalVariable = async (
raw: null,
},
},
pushTriggerItem: (triggerItem: ITriggerItem) => {
pushTriggerItem: (triggerItem) => {
if (
isAlreadyProcessed(triggerItem.meta.internalId) &&
!$.execution.testRun
@@ -109,7 +85,7 @@ const globalVariable = async (
throw new EarlyExitError();
}
},
setActionItem: (actionItem: IActionItem) => {
setActionItem: (actionItem) => {
$.actionOutput.data = actionItem;
},
};
@@ -151,7 +127,7 @@ const globalVariable = async (
? []
: await flow?.lastInternalIds(2000);
const isAlreadyProcessed = (internalId: string) => {
const isAlreadyProcessed = (internalId) => {
return lastInternalIds?.includes(internalId);
};

View File

@@ -6,9 +6,9 @@ import { addResolversToSchema } from '@graphql-tools/schema';
import { applyMiddleware } from 'graphql-middleware';
import appConfig from '../config/app';
import logger from '../helpers/logger';
import authentication from '../helpers/authentication';
import * as Sentry from '../helpers/sentry.ee';
import logger from './logger';
import authentication from './authentication';
import * as Sentry from './sentry.ee';
import resolvers from '../graphql/resolvers';
import HttpError from '../errors/http';
@@ -28,7 +28,7 @@ const graphQLInstance = graphqlHTTP({
logger.error(error.path + ' : ' + error.message + '\n' + error.stack);
if (error.originalError instanceof HttpError) {
delete (error.originalError as HttpError).response;
delete error.originalError.response;
}
Sentry.captureException(error, {
@@ -36,9 +36,9 @@ const graphQLInstance = graphqlHTTP({
extra: {
source: error.source?.body,
positions: error.positions,
path: error.path
}
})
path: error.path,
},
});
return error;
},

View File

@@ -1,14 +1,10 @@
import { IHttpClientParams } from '@automatisch/types';
import { InternalAxiosRequestConfig } from 'axios';
import { URL } from 'node:url';
export { AxiosInstance as IHttpClient } from 'axios';
import HttpError from '../../errors/http';
import axios from '../axios-with-proxy';
const removeBaseUrlForAbsoluteUrls = (
requestConfig: InternalAxiosRequestConfig
): InternalAxiosRequestConfig => {
const removeBaseUrlForAbsoluteUrls = (requestConfig) => {
try {
const url = new URL(requestConfig.url);
requestConfig.baseURL = url.origin;
@@ -20,33 +16,27 @@ const removeBaseUrlForAbsoluteUrls = (
}
};
export default function createHttpClient({
$,
baseURL,
beforeRequest = [],
}: IHttpClientParams) {
export default function createHttpClient({ $, baseURL, beforeRequest = [] }) {
const instance = axios.create({
baseURL,
});
instance.interceptors.request.use(
(requestConfig: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
const newRequestConfig = removeBaseUrlForAbsoluteUrls(requestConfig);
instance.interceptors.request.use((requestConfig) => {
const newRequestConfig = removeBaseUrlForAbsoluteUrls(requestConfig);
const result = beforeRequest.reduce((newConfig, beforeRequestFunc) => {
return beforeRequestFunc($, newConfig);
}, newRequestConfig);
const result = beforeRequest.reduce((newConfig, beforeRequestFunc) => {
return beforeRequestFunc($, newConfig);
}, newRequestConfig);
/**
* axios seems to want InternalAxiosRequestConfig returned not AxioRequestConfig
* anymore even though requests do require AxiosRequestConfig.
*
* Since both interfaces are very similar (InternalAxiosRequestConfig
* extends AxiosRequestConfig), we can utilize an assertion below
**/
return result as InternalAxiosRequestConfig;
}
);
/**
* axios seems to want InternalAxiosRequestConfig returned not AxioRequestConfig
* anymore even though requests do require AxiosRequestConfig.
*
* Since both interfaces are very similar (InternalAxiosRequestConfig
* extends AxiosRequestConfig), we can utilize an assertion below
**/
return result;
});
instance.interceptors.response.use(
(response) => response,

View File

@@ -1,12 +1,7 @@
import { Application } from 'express';
import { ExpressAdapter } from '@bull-board/express';
import basicAuth from 'express-basic-auth';
import appConfig from '../config/app';
const injectBullBoardHandler = async (
app: Application,
serverAdapter: ExpressAdapter
) => {
const injectBullBoardHandler = async (app, serverAdapter) => {
if (
!appConfig.enableBullMQDashboard ||
!appConfig.bullMQDashboardUsername ||

View File

@@ -1,14 +1,13 @@
import morgan, { StreamOptions } from 'morgan';
import { Request } from 'express';
import morgan from 'morgan';
import logger from './logger';
const stream: StreamOptions = {
const stream = {
write: (message) =>
logger.http(message.substring(0, message.lastIndexOf('\n'))),
};
const registerGraphQLToken = () => {
morgan.token('graphql-query', (req: Request) => {
morgan.token('graphql-query', (req) => {
if (req.body.query) {
return `GraphQL ${req.body.query}`;
}

View File

@@ -1,12 +1,4 @@
import { Model } from 'objection';
import ExtendedQueryBuilder from '../models/query-builder';
import type Base from '../models/base';
const paginate = async (
query: ExtendedQueryBuilder<Model, Model[]>,
limit: number,
offset: number,
) => {
const paginate = async (query, limit, offset) => {
if (limit < 1 || limit > 100) {
throw new Error('Limit must be between 1 and 100');
}
@@ -22,7 +14,7 @@ const paginate = async (
totalPages: Math.ceil(count / limit),
},
totalCount: count,
edges: records.map((record: Base) => ({
edges: records.map((record) => ({
node: record,
})),
};

View File

@@ -1,38 +1,16 @@
type TParameters = {
[key: string]: string;
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 = {};
export default function parseLinkHeader(link) {
const parsed = {};
if (!link) return parsed;
const items = link.split(',');
for (const item of items) {
const [rawUriReference, ...rawLinkParameters] = item.split(';') as [
string,
...string[]
];
const [rawUriReference, ...rawLinkParameters] = item.split(';');
const trimmedUriReference = rawUriReference.trim();
const reference = trimmedUriReference.slice(1, -1);
const parameters: TParameters = {};
const parameters = {};
for (const rawParameter of rawLinkParameters) {
const trimmedRawParameter = rawParameter.trim();
@@ -41,7 +19,7 @@ export default function parseLinkHeader(link: string): TParsedLinkHeader {
parameters[key.trim()] = value.slice(1, -1);
}
parsed[parameters.rel as TRel] = {
parsed[parameters.rel] = {
uri: reference,
parameters,
};

View 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);
}
);
}

View File

@@ -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);
}
);
};

View File

@@ -1,13 +1,11 @@
import { Express } from 'express';
import * as Sentry from '@sentry/node';
import type { CaptureContext } from '@sentry/types';
import * as Tracing from '@sentry/tracing';
import appConfig from '../config/app';
const isSentryEnabled = !!appConfig.sentryDsn;
export function init(app?: Express) {
export function init(app) {
if (!isSentryEnabled) return;
return Sentry.init({
@@ -22,31 +20,32 @@ export function init(app?: Express) {
});
}
export function attachRequestHandler(app: Express) {
export function attachRequestHandler(app) {
if (!isSentryEnabled) return;
app.use(Sentry.Handlers.requestHandler());
}
export function attachTracingHandler(app: Express) {
export function attachTracingHandler(app) {
if (!isSentryEnabled) return;
app.use(Sentry.Handlers.tracingHandler());
}
export function attachErrorHandler(app: Express) {
export function attachErrorHandler(app) {
if (!isSentryEnabled) return;
app.use(Sentry.Handlers.errorHandler({
shouldHandleError() {
// TODO: narrow down the captured errors in time as we receive samples
return true;
}
}));
app.use(
Sentry.Handlers.errorHandler({
shouldHandleError() {
// 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;
return Sentry.captureException(exception, captureContext);

View File

@@ -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 instanceId from './instance-id';
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';
const WRITE_KEY = '284Py4VgK2MsNYV7xlKzyrALx0v';
@@ -15,22 +10,17 @@ const CPUS = os.cpus();
const SIX_HOURS_IN_MILLISECONDS = 21600000;
class Telemetry {
organizationId: string;
instanceId: string;
client: Analytics;
serviceType: string;
constructor() {
this.client = new Analytics(WRITE_KEY, DATA_PLANE_URL);
this.organizationId = organizationId();
this.instanceId = instanceId();
}
setServiceType(type: string) {
setServiceType(type) {
this.serviceType = type;
}
track(name: string, properties: apiObject) {
track(name, properties) {
if (!appConfig.telemetryEnabled) {
return;
}
@@ -48,7 +38,7 @@ class Telemetry {
});
}
stepCreated(step: Step) {
stepCreated(step) {
this.track('stepCreated', {
stepId: step.id,
flowId: step.flowId,
@@ -57,7 +47,7 @@ class Telemetry {
});
}
stepUpdated(step: Step) {
stepUpdated(step) {
this.track('stepUpdated', {
stepId: step.id,
flowId: step.flowId,
@@ -71,7 +61,7 @@ class Telemetry {
});
}
flowCreated(flow: Flow) {
flowCreated(flow) {
this.track('flowCreated', {
flowId: flow.id,
name: flow.name,
@@ -81,7 +71,7 @@ class Telemetry {
});
}
flowUpdated(flow: Flow) {
flowUpdated(flow) {
this.track('flowUpdated', {
flowId: flow.id,
name: flow.name,
@@ -91,7 +81,7 @@ class Telemetry {
});
}
executionCreated(execution: Execution) {
executionCreated(execution) {
this.track('executionCreated', {
executionId: execution.id,
flowId: execution.flowId,
@@ -101,7 +91,7 @@ class Telemetry {
});
}
executionStepCreated(executionStep: ExecutionStep) {
executionStepCreated(executionStep) {
this.track('executionStepCreated', {
executionStepId: executionStep.id,
executionId: executionStep.executionId,
@@ -112,7 +102,7 @@ class Telemetry {
});
}
connectionCreated(connection: Connection) {
connectionCreated(connection) {
this.track('connectionCreated', {
connectionId: connection.id,
key: connection.key,
@@ -122,7 +112,7 @@ class Telemetry {
});
}
connectionUpdated(connection: Connection) {
connectionUpdated(connection) {
this.track('connectionUpdated', {
connectionId: connection.id,
key: connection.key,

View File

@@ -1,20 +1,23 @@
import { PureAbility, fieldPatternMatcher, mongoQueryMatcher } from '@casl/ability';
import type User from '../models/user'
import {
PureAbility,
fieldPatternMatcher,
mongoQueryMatcher,
} from '@casl/ability';
// 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 role = user?.role;
// We're not using mongo, but our fields, conditions match
const options = {
conditionsMatcher: mongoQueryMatcher,
fieldMatcher: fieldPatternMatcher
fieldMatcher: fieldPatternMatcher,
};
if (!role || !permissions) {
return new PureAbility([], options);
}
return new PureAbility<[string, string], string[]>(permissions, options);
return new PureAbility(permissions, options);
}

View File

@@ -1,8 +1,8 @@
import express, { Application } from 'express';
import express from 'express';
import { dirname, join } from 'path';
import appConfig from '../config/app';
const webUIHandler = async (app: Application) => {
const webUIHandler = async (app) => {
if (appConfig.serveWebAppSeparately) return;
const webAppPath = require.resolve('@automatisch/web');

View File

@@ -1,5 +1,3 @@
import { Response } from 'express';
import { IRequest } from '@automatisch/types';
import isEmpty from 'lodash/isEmpty';
import Flow from '../models/flow';
@@ -12,11 +10,7 @@ import {
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
} from './remove-job-configuration';
export default async (
flowId: string,
request: IRequest,
response: Response
) => {
export default async (flowId, request, response) => {
const flow = await Flow.query().findById(flowId).throwIfNotFound();
const user = await flow.$relatedQuery('user');