Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add V4 requests #926

Merged
merged 12 commits into from Nov 3, 2021
Merged
2 changes: 1 addition & 1 deletion .eslintrc.yaml
Expand Up @@ -39,4 +39,4 @@ rules:
jsx-a11y/no-static-element-interactions: 'off'
parserOptions:
sourceType: module
ecmaVersion: 9
ecmaVersion: latest
2 changes: 1 addition & 1 deletion .github/workflows/test-pulls.yml
Expand Up @@ -20,7 +20,7 @@ jobs:

# format tests don't pass on node 12 since icu is missing and tests don't work with locales
- name: Test packages
run: yarn test --ignore='@lowdefy/format' --ignore='@lowdefy/blocks-*'
run: yarn test --ignore='@lowdefy/format' --ignore='@lowdefy/blocks-*' --ignore='@lowdefy/graphql'

- name: Upload coverage to codecov
run: bash <(curl -s https://codecov.io/bash)
Expand Down
249 changes: 203 additions & 46 deletions .pnp.cjs

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion .yarn/sdks/eslint/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint",
"version": "7.27.0-sdk",
"version": "8.1.0-sdk",
"main": "./lib/api.js",
"type": "commonjs"
}
2 changes: 1 addition & 1 deletion .yarn/sdks/prettier/package.json
@@ -1,6 +1,6 @@
{
"name": "prettier",
"version": "2.3.0-sdk",
"version": "2.4.1-sdk",
"main": "./index.js",
"type": "commonjs"
}
1 change: 1 addition & 0 deletions lerna.json
Expand Up @@ -3,6 +3,7 @@
"packages": [
"src/packages/*",
"src/packages/blocks/*",
"src/packages/connections/*",
"src/packages/servers/*"
],
"npmClient": "yarn",
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -25,6 +25,7 @@
"workspaces": [
"packages/*",
"packages/blocks/*",
"packages/connections/*",
"packages/servers/*"
],
"scripts": {
Expand Down
3 changes: 2 additions & 1 deletion packages/api/package.json
Expand Up @@ -29,7 +29,8 @@
"dist/*"
],
"scripts": {
"build": "yarn webpack",
"babel": "babel src --out-dir dist",
"build": "yarn clean && yarn babel",
"clean": "rm -rf dist",
"prepare": "yarn build",
"test": "jest --coverage",
Expand Down
10 changes: 6 additions & 4 deletions packages/api/src/context/createContext.js
Expand Up @@ -18,14 +18,16 @@ import createAuthorize from './createAuthorize';
import createReadConfigFile from './readConfigFile';
import verifyAuthorizationHeader from './verifyAuthorizationHeader';

async function createContext({ configDirectory, getSecrets }) {
const readConfigFile = createReadConfigFile({ configDirectory });
const [config, secrets] = await Promise.all([readConfigFile('config.json'), getSecrets()]);
function contextFn({ headers, host, protocol, setHeader }) {
async function createContext({ buildDirectory, connections, secrets }) {
const readConfigFile = createReadConfigFile({ buildDirectory });
const config = await readConfigFile('config.json');
function contextFn({ headers, host, logger, protocol, setHeader }) {
const context = {
config,
connections,
headers,
host,
logger,
protocol,
readConfigFile,
secrets,
Expand Down
27 changes: 20 additions & 7 deletions packages/api/src/context/createContext.test.js
Expand Up @@ -23,13 +23,15 @@ jest.mock('./createAuthorize');
jest.mock('./readConfigFile');
jest.mock('./verifyAuthorizationHeader');

const getSecrets = jest.fn();

getSecrets.mockImplementation(() => ({ secret: true }));
const connections = { Connection: true };
const secrets = { secret: true };

createAuthorize.mockImplementation(({ authenticated, roles = [] }) => ({ authenticated, roles }));

createReadConfigFile.mockImplementation(({ configDirectory }) => () => ({ configDirectory }));
createReadConfigFile.mockImplementation(({ buildDirectory }) => (path) => ({
buildDirectory,
path,
}));

verifyAuthorizationHeader.mockImplementation(() => ({
authenticated: true,
Expand All @@ -38,10 +40,11 @@ verifyAuthorizationHeader.mockImplementation(() => ({
}));

test('createContext', async () => {
const contextFn = await createContext({ configDirectory: 'configDirectory', getSecrets });
const contextFn = await createContext({ connections, buildDirectory: 'buildDirectory', secrets });
const context = contextFn({
headers: { header: 'header' },
host: 'host',
logger: 'logger',
protocol: 'https',
setHeader: 'setHeaderFunction',
});
Expand All @@ -55,12 +58,17 @@ test('createContext', async () => {
],
},
"config": Object {
"configDirectory": "configDirectory",
"buildDirectory": "buildDirectory",
"path": "config.json",
},
"connections": Object {
"Connection": true,
},
"headers": Object {
"header": "header",
},
"host": "host",
"logger": "logger",
"protocol": "https",
"readConfigFile": [Function],
"secrets": Object {
Expand All @@ -84,12 +92,17 @@ test('createContext', async () => {
],
},
"config": Object {
"configDirectory": "configDirectory",
"buildDirectory": "buildDirectory",
"path": "config.json",
},
"connections": Object {
"Connection": true,
},
"headers": Object {
"header": "header",
},
"host": "host",
"logger": "logger",
"protocol": "https",
"readConfigFile": [Function],
"secrets": Object {
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/context/readConfigFile.js
Expand Up @@ -17,9 +17,9 @@
import path from 'path';
import { cachedPromises, getFileExtension, readFile } from '@lowdefy/node-utils';

function createReadConfigFile({ configDirectory }) {
function createReadConfigFile({ buildDirectory }) {
async function readConfigFile(filePath) {
const fileContent = await readFile(path.resolve(configDirectory, filePath));
const fileContent = await readFile(path.resolve(buildDirectory, filePath));
if (getFileExtension(filePath) === 'json') {
return JSON.parse(fileContent);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/index.js
Expand Up @@ -22,6 +22,7 @@ import openIdLogoutUrl from './routes/auth/openIdLogoutUrl';
import pageConfig from './routes/page/pageConfig';
import pageHtml from './routes/page/pageHtml';
import rootConfig from './routes/rootConfig/rootConfig';
import request from './routes/request/request';

import {
AuthenticationError,
Expand All @@ -40,6 +41,7 @@ export {
pageConfig,
pageHtml,
rootConfig,
request,
AuthenticationError,
ConfigurationError,
RequestError,
Expand Down
28 changes: 28 additions & 0 deletions packages/api/src/routes/request/authorizeRequest.js
@@ -0,0 +1,28 @@
/*
Copyright 2020-2021 Lowdefy, Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { ConfigurationError } from '../../context/errors';

function authorizeRequest({ authorize, logger }, { requestConfig }) {
if (!authorize(requestConfig)) {
logger.warn({ authorized: false }, 'Unauthorized Request');
// Throw does not exist error to avoid leaking information that request exists to unauthorized users
throw new ConfigurationError(`Request "${requestConfig.requestId}" does not exist.`);
}
logger.debug({ authorized: true }, 'Authorize Request');
}

export default authorizeRequest;
39 changes: 39 additions & 0 deletions packages/api/src/routes/request/callRequestResolver.js
@@ -0,0 +1,39 @@
/*
Copyright 2020-2021 Lowdefy, Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { RequestError } from '../../context/errors';

async function callRequestResolver(
{ logger },
{ connectionProperties, requestConfig, requestProperties, requestHandler }
) {
try {
const response = await requestHandler.resolver({
request: requestProperties,
connection: connectionProperties,
});
return response;
} catch (error) {
const err = new RequestError(error.message);
logger.debug(
{ params: { id: requestConfig.requestId, type: requestConfig.type }, err },
err.message
);
throw err;
}
}

export default callRequestResolver;
37 changes: 37 additions & 0 deletions packages/api/src/routes/request/checkConnectionRead.js
@@ -0,0 +1,37 @@
/*
Copyright 2020-2021 Lowdefy, Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { ConfigurationError } from '../../context/errors';

function checkConnectionRead(
{ logger },
{ connectionConfig, connectionProperties, requestConfig, requestHandler }
) {
if (requestHandler.checkRead && connectionProperties.read === false) {
const err = new ConfigurationError(
`Connection "${connectionConfig.connectionId}" does not allow reads.`
);
logger.debug(
{
params: { connectionId: connectionConfig.connectionId, requestId: requestConfig.requestId },
err,
},
err.message
);
throw err;
}
}

export default checkConnectionRead;
37 changes: 37 additions & 0 deletions packages/api/src/routes/request/checkConnectionWrite.js
@@ -0,0 +1,37 @@
/*
Copyright 2020-2021 Lowdefy, Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { ConfigurationError } from '../../context/errors';

function checkConnectionWrite(
{ logger },
{ connectionConfig, connectionProperties, requestConfig, requestHandler }
) {
if (requestHandler.checkWrite && connectionProperties.write !== true) {
const err = new ConfigurationError(
`Connection "${connectionConfig.connectionId}" does not allow writes.`
);
logger.debug(
{
params: { connectionId: connectionConfig.connectionId, requestId: requestConfig.requestId },
err,
},
err.message
);
throw err;
}
}

export default checkConnectionWrite;
50 changes: 50 additions & 0 deletions packages/api/src/routes/request/evaluateOperators.js
@@ -0,0 +1,50 @@
/*
Copyright 2020-2021 Lowdefy, Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { NodeParser } from '@lowdefy/operators';

import { RequestError } from '../../context/errors';

async function evaluateOperators({ secrets, user }, { connectionConfig, payload, requestConfig }) {
const operatorsParser = new NodeParser({
payload,
secrets,
user,
});
await operatorsParser.init();
const { output: connectionProperties, errors: connectionErrors } = operatorsParser.parse({
input: connectionConfig.properties || {},
location: connectionConfig.connectionId,
});
if (connectionErrors.length > 0) {
throw new RequestError(connectionErrors[0]);
}

const { output: requestProperties, errors: requestErrors } = operatorsParser.parse({
input: requestConfig.properties || {},
location: requestConfig.requestId,
});
if (requestErrors.length > 0) {
throw new RequestError(requestErrors[0]);
}

return {
connectionProperties,
requestProperties,
};
}

export default evaluateOperators;