From 4a6b1230a7093d719234ceddeba6d12c2d6e07a8 Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Mon, 5 Oct 2020 18:46:53 +0000 Subject: [PATCH 01/27] Fix refresh --- pages/job/[botId]/[id].tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/job/[botId]/[id].tsx b/pages/job/[botId]/[id].tsx index 0cbc481..5119ea7 100644 --- a/pages/job/[botId]/[id].tsx +++ b/pages/job/[botId]/[id].tsx @@ -18,8 +18,8 @@ const BotView = ({ jobInfo, botInfo }: AppProps) => { const refresh = async (botId, jobId) => { setLoading(true) const hostname = process.env.HOST_URL || 'http://localhost:3000' - const res = await fetch(`${hostname}/api/botcommand/jobs/${botId}/${jobId}`) - const botRes = await fetch(`${hostname}/api/botcommand/bots/${botId}`) + const res = await fetch(`${hostname}/api/botcommander/jobs/${botId}/${jobId}`) + const botRes = await fetch(`${hostname}/api/botcommander/bots/${botId}`) const refreshedBotInfo = await botRes.json() const refreshedJobInfo = await res.json() setBot(refreshedBotInfo) From 7ea7f41ad32c71c49c15402d780d8cb61d666b76 Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Mon, 5 Oct 2020 18:47:29 +0000 Subject: [PATCH 02/27] Less spellchecky --- .vscode/settings.json | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0b4fbaf..9b9d7f7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,7 +13,14 @@ Happy linting! 💖 */ { "cSpell.words": [ - "botcommander", - "esnext" - ] + "Jobkey", + "Robocloud", + "botcommander", + "esnext", + "odata", + "robo", + "uipath", + "tailwindcss" + ], + "cSpell.diagnosticLevel": "Hint", } From 6ffb4198db11179000af0663dbc8ba2e3d7553e4 Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Mon, 5 Oct 2020 18:56:03 +0000 Subject: [PATCH 03/27] Add 'Running' to Pending states --- server/models/UiPathJob.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/models/UiPathJob.ts b/server/models/UiPathJob.ts index 41c1baf..4edf553 100644 --- a/server/models/UiPathJob.ts +++ b/server/models/UiPathJob.ts @@ -34,6 +34,8 @@ export class UiPathJob extends Job { return JobState.Complete } else if (state === 'Pending') { return JobState.Pending + } else if (state === 'Running') { + return JobState.Pending } return JobState.Failed } From ca242e5bf01f9a45058ce0e6793ade57c458a397 Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Mon, 5 Oct 2020 19:07:52 +0000 Subject: [PATCH 04/27] Args passed correctly again - Fixed uncontrolled render error --- components/InputArgsFields.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/InputArgsFields.tsx b/components/InputArgsFields.tsx index efcaab0..a4131d1 100644 --- a/components/InputArgsFields.tsx +++ b/components/InputArgsFields.tsx @@ -24,11 +24,13 @@ const InputArgsFields: FunctionComponent<{ inputArgs: InputArgs[], inputArgsChan
{Object.values(inputArgs).map(inputArg => (
- +
-
From 9aa2fc9fdc0e34299610309f3d0a37bb8b45cbcd Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Mon, 5 Oct 2020 19:14:35 +0000 Subject: [PATCH 05/27] Fix client side access to hostUrl - We need to pass the hostname to the component in order to get it client-side --- pages/job/[botId]/[id].tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pages/job/[botId]/[id].tsx b/pages/job/[botId]/[id].tsx index 5119ea7..e389ecb 100644 --- a/pages/job/[botId]/[id].tsx +++ b/pages/job/[botId]/[id].tsx @@ -9,7 +9,7 @@ import classNames from 'classnames' const DynamicReactJson = dynamic(import('react-json-view'), { ssr: false }) -const BotView = ({ jobInfo, botInfo }: AppProps) => { +const BotView = ({ jobInfo, botInfo, hostUrl }: AppProps) => { const [loading, setLoading] = useState(false) const [bot, setBot] = useState(botInfo) const [job, setJob] = useState(jobInfo) @@ -17,9 +17,8 @@ const BotView = ({ jobInfo, botInfo }: AppProps) => { const refresh = async (botId, jobId) => { setLoading(true) - const hostname = process.env.HOST_URL || 'http://localhost:3000' - const res = await fetch(`${hostname}/api/botcommander/jobs/${botId}/${jobId}`) - const botRes = await fetch(`${hostname}/api/botcommander/bots/${botId}`) + const res = await fetch(`${hostUrl}/api/botcommander/jobs/${botId}/${jobId}`) + const botRes = await fetch(`${hostUrl}/api/botcommander/bots/${botId}`) const refreshedBotInfo = await botRes.json() const refreshedJobInfo = await res.json() setBot(refreshedBotInfo) @@ -101,16 +100,17 @@ export const getServerSideProps: GetServerSideProps = async (ctx: GetServerSideP // console.log(session) const query = ctx.query const { botId, id } = query - const hostname = process.env.HOST_URL || 'http://localhost:3000' + const hostUrl = process.env.HOST_URL || 'http://localhost:3000' const options = { headers: { cookie: ctx.req.headers.cookie } } - const res = await fetch(`${hostname}/api/botcommander/jobs/${botId}/${id}`, options) - const botRes = await fetch(`${hostname}/api/botcommander/bots/${botId}`, options) + const res = await fetch(`${hostUrl}/api/botcommander/jobs/${botId}/${id}`, options) + const botRes = await fetch(`${hostUrl}/api/botcommander/bots/${botId}`, options) const botInfo = await botRes.json() const jobInfo = await res.json() return { props: { jobInfo, botInfo, + hostUrl, }, } } From 138bfa901070b346a65ab46c358330e241c8bc1a Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Mon, 5 Oct 2020 19:27:33 +0000 Subject: [PATCH 06/27] Update name and imate --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 660302a..daaeabc 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,11 @@ - [Formulated Automation Podcast](https://www.formulatedautomation.com/category/podcast/) -# FormulatedAutomation-Profiler +# Bot Commander [![FormulatedAutomation](https://circleci.com/gh/FormulatedAutomation/Profiler.svg?style=shield)](https://app.circleci.com/pipelines/github/FormulatedAutomation/Profiler) -[![PyPI version](https://badge.fury.io/py/fa-profiler.svg)](https://badge.fury.io/py/fa-profiler) -![image](https://user-images.githubusercontent.com/2868/86496363-2473ff00-bd4b-11ea-868a-ee07a2ace9d9.png) +![image](https://user-images.githubusercontent.com/2868/95122864-20c39000-071f-11eb-86c8-63820b013ae4.png) ### Introduction From 45f30454871d8487120d1e7a5c9c4becff220d0c Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Mon, 5 Oct 2020 19:54:42 +0000 Subject: [PATCH 07/27] Ensure we redirect to the appropriate page durring signin --- lib/utils.ts | 3 +++ pages/auth/signin.tsx | 23 +++++++++++++++++++---- pages/index.js | 2 -- 3 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 lib/utils.ts diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..054749e --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,3 @@ +export function firstArrayElement(arrOrString: string[] | string): string { + return Array.isArray(arrOrString) ? arrOrString[0] : arrOrString +} \ No newline at end of file diff --git a/pages/auth/signin.tsx b/pages/auth/signin.tsx index 16eb4d7..bdf3f94 100644 --- a/pages/auth/signin.tsx +++ b/pages/auth/signin.tsx @@ -1,9 +1,24 @@ -import React from 'react' -import { providers as getProviders, signIn } from 'next-auth/client' -import PublicLayout from '../../components/PublicLayout' +import React, { useEffect } from 'react' +import { providers as getProviders, signIn, useSession } from 'next-auth/client' import { Providers } from 'next-auth/providers' -export default function SignIn ({ providers }: {providers: Providers}) { +import PublicLayout from '../../components/PublicLayout' +import Router, { useRouter } from 'next/router' +import { firstArrayElement } from '../../lib/utils' + +export default function SignIn ({ providers }: {providers: Providers }) { + const router = useRouter() + const [session] = useSession() + + useEffect(() => { + if (session) { + if (router.query.callbackUrl) { + Router.push(firstArrayElement(router.query.callbackUrl)) + } + Router.push('/bots') + } + }, [session]) + return (
diff --git a/pages/index.js b/pages/index.js index 04cab38..2e25e09 100644 --- a/pages/index.js +++ b/pages/index.js @@ -7,8 +7,6 @@ export default function Page () { const [session, loading] = useSession() useEffect(() => { - console.log(session) - console.log(loading) if (session) { Router.push('/bots') } else if (session === null) { From 5cd00fdc0953fdda70d134edf9edde94173e1532 Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Tue, 6 Oct 2020 13:49:04 +0000 Subject: [PATCH 08/27] Linter fixes --- lib/utils.ts | 4 ++-- pages/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/utils.ts b/lib/utils.ts index 054749e..d91d7d9 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,3 +1,3 @@ -export function firstArrayElement(arrOrString: string[] | string): string { +export function firstArrayElement (arrOrString: string[] | string): string { return Array.isArray(arrOrString) ? arrOrString[0] : arrOrString -} \ No newline at end of file +} diff --git a/pages/index.js b/pages/index.js index 2e25e09..50c95f0 100644 --- a/pages/index.js +++ b/pages/index.js @@ -4,7 +4,7 @@ import { useSession } from 'next-auth/client' import PublicLayout from '../components/PublicLayout' export default function Page () { - const [session, loading] = useSession() + const [session] = useSession() useEffect(() => { if (session) { From 8aff177657cb3993fd2e7bc020b141f48c924fc7 Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Tue, 6 Oct 2020 15:15:21 +0000 Subject: [PATCH 09/27] Funcional tests of the API - #15 --- lib/acl.ts | 5 +- package.json | 5 +- pages/api/botcommander/__tests__/ping_test.ts | 48 ++++++++++++++++++- server/token.ts | 7 ++- tests/api_test_helper.ts | 32 +++++++++---- yarn.lock | 12 +++++ 6 files changed, 94 insertions(+), 15 deletions(-) diff --git a/lib/acl.ts b/lib/acl.ts index 9067a81..8b9cbe1 100644 --- a/lib/acl.ts +++ b/lib/acl.ts @@ -40,7 +40,10 @@ export class ACL { } // Get back a list of bots that a user is allowed to use - listBots (token: Token): Bot[] { + listBots (token: Token | null): Bot[] { + if (!token) { + return [] + } const allowedBots = [] for (const bot of this.bots) { const botAclGroups = bot.botConfig.acl.groups diff --git a/package.json b/package.json index d97e855..4e59425 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,14 @@ "build": "next build", "start": "next start -p $PORT", "test": "jest", - "lint": "eslint \"pages/**\" \"lib/**\" \"server/**\" \"components/**\"", + "lint": "eslint \"pages/**\" \"lib/**\" \"server/**\" \"components/**\" \"tests/**\"", + "lint-fix": "eslint \"pages/**\" \"lib/**\" \"server/**\" \"components/**\" \"tests/**\" --fix", "heroku-postbuild": "npm run build" }, "dependencies": { "@fullhuman/postcss-purgecss": "^2.1.0", "@tailwindcss/ui": "^0.1.3", + "@types/test-listen": "^1.1.0", "@zeit/next-css": "^1.0.1", "classnames": "^2.2.6", "config": "^3.3.1", @@ -27,6 +29,7 @@ "react-dom": "16.13.1", "react-json-view": "^1.19.1", "tailwindcss": "^1.2.0", + "test-listen": "^1.1.0", "tsx": "^1.0.0", "uipath-orchestrator": "^1.1.3", "winston": "^3.3.3" diff --git a/pages/api/botcommander/__tests__/ping_test.ts b/pages/api/botcommander/__tests__/ping_test.ts index 3fcb132..5d03648 100644 --- a/pages/api/botcommander/__tests__/ping_test.ts +++ b/pages/api/botcommander/__tests__/ping_test.ts @@ -1,6 +1,19 @@ -import { handler } from '../ping' +import http from 'http' +import fetch from 'node-fetch' +import listen from 'test-listen' import { createMocks } from 'node-mocks-http' -import { loggedInContext } from '../../../../tests/api_test_helper' +import { apiResolver } from 'next/dist/next-server/server/api-utils' + +import { authedCookie, loggedInContext } from '../../../../tests/api_test_helper' +import Ping, { handler } from '../ping' + +const servers = [] + +afterAll(() => { + for (const server of servers) { + server.close() + } +}) it('Should return the authenticated users token', async () => { const { req, res } = createMocks({ @@ -11,3 +24,34 @@ it('Should return the authenticated users token', async () => { const data = res._getJSONData() expect(data.token.email).toEqual('m@mdp.im') }) + +describe('/api/botcommander/ping', () => { + test('responds 401 to unauthed GET', async () => { + expect.assertions(1) + const requestHandler = (req, res) => { + return apiResolver(req, res, undefined, Ping, null, false) + } + const server = http.createServer(requestHandler) + servers.push(server) + const url = await listen(server) + const response = await fetch(url) + expect(response.status).toBe(401) + }) + + test('responds 200 to authed GET', async () => { + expect.assertions(1) + const requestHandler = (req, res) => { + return apiResolver(req, res, undefined, Ping, null, false) + } + const server = http.createServer(requestHandler) + servers.push(server) + const url = await listen(server) + const opts = { + headers: { + cookie: await authedCookie('m@mdp.im'), + }, + } + const response = await fetch(url, opts) + expect(response.status).toBe(200) + }) +}) diff --git a/server/token.ts b/server/token.ts index b02dabb..ab7d03b 100644 --- a/server/token.ts +++ b/server/token.ts @@ -11,7 +11,12 @@ export interface Token { exp: number } -export async function getToken (req: NextApiRequest): Promise { +export async function buildToken (token: Token): Promise { + const config = await getConfig() + return jwt.encode({ token: token, secret: config.secret }) +} + +export async function getToken (req: NextApiRequest): Promise { const config = await getConfig() return (await jwt.getToken({ req, secret: config.secret }) as Token) } diff --git a/tests/api_test_helper.ts b/tests/api_test_helper.ts index 8faaa4d..78c2fd9 100644 --- a/tests/api_test_helper.ts +++ b/tests/api_test_helper.ts @@ -1,18 +1,30 @@ import { get as getConfig } from '../lib/config' -import { Token } from '../server/token' -import { BotCommanderContext } from "../server/middleware/context"; +import { buildToken, Token } from '../server/token' +import { BotCommanderContext } from '../server/middleware/context' -export async function loggedInContext(email: string): Promise { +const ONE_MONTH = 20 * 24 * 60 * 60 + +function buildFakeToken (email: string): Token { + return { + email, + name: 'Test User Token', + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + ONE_MONTH, + } +} + +export async function loggedInContext (email: string): Promise { const config = await getConfig() return { acl: config.acl, bots: config.bots, - token: { - email, - name: 'Test User Token', - iat: 9999999999, - exp: 9999999999, - } + token: buildFakeToken(email), } +} -} \ No newline at end of file +export async function authedCookie (email: string): Promise { + const token = buildFakeToken(email) + const jwtToken = await buildToken(token) + const cookieStr = `next-auth.session-token=${jwtToken}; __Secure-next-auth.session-token=${jwtToken}` + return cookieStr +} diff --git a/yarn.lock b/yarn.lock index df731c1..4059448 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1559,6 +1559,13 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/test-listen@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/test-listen/-/test-listen-1.1.0.tgz#db7e5b0e277b4a3ee7b90770dae1a41b186458af" + integrity sha512-y6ZfbSzYHniCeY6ZAzsQjSAdJInNVoEz4Uhsb81W+RCoNYA59yoG/+XbqPqCPj2KCU3Wa6RFWSozutkGIHIsNQ== + dependencies: + "@types/node" "*" + "@types/testing-library__jest-dom@^5.9.1": version "5.9.3" resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.3.tgz#574039e210140a536c6ec891063289fb742a75eb" @@ -8916,6 +8923,11 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +test-listen@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/test-listen/-/test-listen-1.1.0.tgz#2ba614d96c3bc9157469003027b42a495dd83b6a" + integrity sha512-OyEVi981C1sb9NX1xayfgZls3p8QTDRwp06EcgxSgd1kktaENBW8dO15i8v/7Fi15j0IYQctJzk5J+hyEBId2w== + text-hex@1.0.x: version "1.0.0" resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" From c46a1fc66afc31fe3c457e886776234dd2acb6f6 Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Tue, 6 Oct 2020 15:17:40 +0000 Subject: [PATCH 10/27] Lint config --- config/api.js | 54 ++++++++++++++++++------------------- config/jest/cssTransform.js | 6 ++--- config/secret.ts | 10 +++---- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/config/api.js b/config/api.js index bfa9384..929a31f 100644 --- a/config/api.js +++ b/config/api.js @@ -5,67 +5,67 @@ export const acl = { 'm@mdp.im', 'percival@gmail.com', 'brent@brentsanders.io', - ] + ], }, users: { emails: [ '/^[A-Z0-9._\\-+\\%]+@formulatedautomation.com$/i', '*@fultonworks.co', - ] + ], }, anotherGroup: { emails: [ - 'foo@foo.com' - ] - } - } + 'foo@foo.com', + ], + }, + }, } -export const bots = +export const bots = [ { id: 'Turkish.Lira.to.USD', - name: "Turkish Lira Conversion Bot", - description: "Converts Turkish Lira to USD", + name: 'Turkish Lira Conversion Bot', + description: 'Converts Turkish Lira to USD', source: 'uipath', type: 'uipath', acl: { - groups: ['admins', 'users'] + groups: ['admins', 'users'], }, }, { id: 'Robocloud.Demo', - name: "Code Scraper", - description: "Code Violations Scraper", + name: 'Code Scraper', + description: 'Code Violations Scraper', secret: process.env.CODE_VIOLATION_BOT_SECRET, - workspaceId: "971988d2-33d9-4cdf-acd4-2d7f7b64814e", - processId: "0acf110e-5160-43f4-a82c-9c1b5a2472dc", + workspaceId: '971988d2-33d9-4cdf-acd4-2d7f7b64814e', + processId: '0acf110e-5160-43f4-a82c-9c1b5a2472dc', type: 'robocloud', acl: { - groups: ['admins', 'users'] + groups: ['admins', 'users'], }, }, { id: 'ACL.Demo.Bot', - name: "ACL Demo Bot", - description: "A bot that should be available to no one", - secret: "abc123", - workspaceId: "12345332-33d9-4cdf-acd4-222222222222", - processId: "12323456-5160-43f4-a82c-222222222222", + name: 'ACL Demo Bot', + description: 'A bot that should be available to no one', + secret: 'abc123', + workspaceId: '12345332-33d9-4cdf-acd4-222222222222', + processId: '12323456-5160-43f4-a82c-222222222222', type: 'robocloud', acl: { - groups: ['anotherGroup'] + groups: ['anotherGroup'], }, }, ] -export const sources = +export const sources = { uipath: { - platformHostname: "platform.uipath.com", - path: "trailingspacell/TrailingSpavyun205999", + platformHostname: 'platform.uipath.com', + path: 'trailingspacell/TrailingSpavyun205999', refreshToken: process.env.UIPATH_REFRESH_TOKEN, - serviceInstanceLogicalName: "TrailingSpavyun205999", - clientId: "8DEv1AMNXczW3y4U15LL3jYf62jK93n5", + serviceInstanceLogicalName: 'TrailingSpavyun205999', + clientId: '8DEv1AMNXczW3y4U15LL3jYf62jK93n5', }, -} \ No newline at end of file +} diff --git a/config/jest/cssTransform.js b/config/jest/cssTransform.js index 48d6966..9ec4261 100644 --- a/config/jest/cssTransform.js +++ b/config/jest/cssTransform.js @@ -1,8 +1,8 @@ module.exports = { - process() { + process () { return 'module.exports = {};' }, - getCacheKey() { + getCacheKey () { return 'cssTransform' }, -} \ No newline at end of file +} diff --git a/config/secret.ts b/config/secret.ts index 2286710..1268cf9 100644 --- a/config/secret.ts +++ b/config/secret.ts @@ -1,7 +1,7 @@ // Secret for JWT and Session storage // Will automatically be generated if not found at config/secret.txt import fs from 'fs' import path from 'path' -import {promisify} from 'util' +import { promisify } from 'util' const secretPath = path.join(process.cwd(), '/config/secret.txt') const MIN_SECRET_LEN = 25 // Even in base64, this would be less than 128 bits of entropy @@ -9,7 +9,7 @@ const MIN_SECRET_LEN = 25 // Even in base64, this would be less than 128 bits of const fsExists = promisify(fs.exists) const fsReadFile = promisify(fs.readFile) -export async function get(): Promise { +export async function get (): Promise { let secret = null if (process.env.SECRET) { secret = process.env.SECRET @@ -18,13 +18,13 @@ export async function get(): Promise { } if (!secret) { console.log(`Secret not found, please either set SECRET or create a file at '${secretPath}' containing the secret key`) - console.log(`Exiting...`) + console.log('Exiting...') process.exit(1) } if (secret && secret.length < MIN_SECRET_LEN) { console.log(`Secret is not secure, please set a secret > ${MIN_SECRET_LEN} characters in length.`) - console.log(`Exiting...`) + console.log('Exiting...') process.exit(1) } return secret -} \ No newline at end of file +} From cc6c1608192d459b85ff030ebe144d1c2fcc5ebe Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Tue, 6 Oct 2020 15:47:37 +0000 Subject: [PATCH 11/27] Fix deprecation with fs.exists --- config/secret.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/config/secret.ts b/config/secret.ts index 1268cf9..ac762eb 100644 --- a/config/secret.ts +++ b/config/secret.ts @@ -6,7 +6,16 @@ import { promisify } from 'util' const secretPath = path.join(process.cwd(), '/config/secret.txt') const MIN_SECRET_LEN = 25 // Even in base64, this would be less than 128 bits of entropy -const fsExists = promisify(fs.exists) +const fsExists = (path) => { + return new Promise((resolve) => { + fs.access(path, fs.constants.F_OK, (err) => { + if (err) { + return resolve(false) + } + return resolve(true) + }) + }) +} const fsReadFile = promisify(fs.readFile) export async function get (): Promise { From db84499bb817a62b58715293f7c07e4fb685d04a Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Tue, 6 Oct 2020 15:47:48 +0000 Subject: [PATCH 12/27] Add more auth tests --- config/api.js | 4 ++-- lib/__tests__/acl_test.ts | 2 +- lib/acl.ts | 1 + pages/api/botcommander/__tests__/ping_test.ts | 5 ++++- pages/api/botcommander/ping.ts | 4 +++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/config/api.js b/config/api.js index 929a31f..1ba712f 100644 --- a/config/api.js +++ b/config/api.js @@ -46,9 +46,9 @@ export const bots = }, }, { - id: 'ACL.Demo.Bot', + id: 'No.Access.Bot', name: 'ACL Demo Bot', - description: 'A bot that should be available to no one', + description: 'A bot that should be available to no one - For test purposes', secret: 'abc123', workspaceId: '12345332-33d9-4cdf-acd4-222222222222', processId: '12323456-5160-43f4-a82c-222222222222', diff --git a/lib/__tests__/acl_test.ts b/lib/__tests__/acl_test.ts index 840f911..145a390 100644 --- a/lib/__tests__/acl_test.ts +++ b/lib/__tests__/acl_test.ts @@ -45,6 +45,6 @@ it('should fail against and unauthorized email with regex', () => { it('should list only the bots available to a user', () => { const aclObj = new ACL(acl, botInstances) - expect(aclObj.isAllowed(getEmailToken('m@mdp.im'), 'ACL.Demo.Bot')).toEqual(false) + expect(aclObj.isAllowed(getEmailToken('m@mdp.im'), 'No.Access.Bot')).toEqual(false) expect(aclObj.listBots(getEmailToken('m@mdp.im')).length).toEqual(2) }) diff --git a/lib/acl.ts b/lib/acl.ts index 8b9cbe1..7617203 100644 --- a/lib/acl.ts +++ b/lib/acl.ts @@ -67,6 +67,7 @@ export class ACL { // Returns a Boolean noting is a user is able to access a process isAllowed (token: Token, botId: string): boolean { const bot = this.bots.find((b) => botId === b.id) + if (!bot) return false const botAclGroups = bot.botConfig.acl.groups for (const group of botAclGroups) { const acl = this.acl.groups[group] diff --git a/pages/api/botcommander/__tests__/ping_test.ts b/pages/api/botcommander/__tests__/ping_test.ts index 5d03648..9778ffc 100644 --- a/pages/api/botcommander/__tests__/ping_test.ts +++ b/pages/api/botcommander/__tests__/ping_test.ts @@ -39,7 +39,7 @@ describe('/api/botcommander/ping', () => { }) test('responds 200 to authed GET', async () => { - expect.assertions(1) + expect.assertions(3) const requestHandler = (req, res) => { return apiResolver(req, res, undefined, Ping, null, false) } @@ -53,5 +53,8 @@ describe('/api/botcommander/ping', () => { } const response = await fetch(url, opts) expect(response.status).toBe(200) + const json = await response.json() + expect(json.bots.length).toEqual(2) + expect(json.token.email).toEqual('m@mdp.im') }) }) diff --git a/pages/api/botcommander/ping.ts b/pages/api/botcommander/ping.ts index 3640052..2c1d674 100644 --- a/pages/api/botcommander/ping.ts +++ b/pages/api/botcommander/ping.ts @@ -4,8 +4,10 @@ import { BotCommanderContext, ensureLoggedIn } from '../../../server/middleware/ export const handler = (req: NextApiRequest, res: NextApiResponse, context: BotCommanderContext) => { const token = context.token + const bots = context.bots.map(bot => ({ id: bot.id })) + console.log(bots) res.statusCode = 200 - res.json({ token }) + res.json({ token, bots: bots }) } export default ensureLoggedIn(handler) From 03abf7541ce969732512ddaf37de06d4c538a35e Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Tue, 6 Oct 2020 17:23:30 +0000 Subject: [PATCH 13/27] Lint everything --- .eslintrc.js | 1 + constants/menu.js | 8 ++++---- demo.js | 28 ++++++++++++++-------------- jest.config.js | 2 +- package.json | 4 ++-- postcss.config.js | 3 ++- setupTests.js | 4 ++-- tailwind.config.js | 4 ++-- 8 files changed, 28 insertions(+), 26 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 0b3aa22..d851acf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,6 +17,7 @@ module.exports = { browser: true, 'jest/globals': true, }, + ignorePatterns: ['node_modules/**'], settings: { react: { version: 'detect', diff --git a/constants/menu.js b/constants/menu.js index b5d6d89..187fc49 100644 --- a/constants/menu.js +++ b/constants/menu.js @@ -1,8 +1,8 @@ const MENU = [ { - label: "Bots", - link: '/bots' + label: 'Bots', + link: '/bots', }, -]; +] -export default MENU; +export default MENU diff --git a/demo.js b/demo.js index 5a9130a..e5c3fd5 100644 --- a/demo.js +++ b/demo.js @@ -1,31 +1,31 @@ -const config = require('./config/sources')['uipath'] +const config = require('./config/sources').uipath const { OrchestratorApi } = require('uipath-orchestrator-api-node') -const oc = new OrchestratorApi ( config ) - +const oc = new OrchestratorApi(config) + const main = async () => { const token = await oc.authenticate() const releases = await oc.release.findAll() console.log(releases) const release = releases.find((r) => r.ProcessKey === 'Turkish.Lira.to.USD') - console.log("Starting Process", release.ProcessKey) + console.log('Starting Process', release.ProcessKey) const inputArgs = { inputLira: 3500, } const result = await oc.job._startJobs({ - startInfo: { - ReleaseKey: release.Key, - RobotIds: [], - JobsCount: 1, - Strategy: 'JobsCount', - InputArguments: JSON.stringify(inputArgs), - } - }) + startInfo: { + ReleaseKey: release.Key, + RobotIds: [], + JobsCount: 1, + Strategy: 'JobsCount', + InputArguments: JSON.stringify(inputArgs), + }, + }) console.log(result.value) } - + if (!module.parent) { main() -} \ No newline at end of file +} diff --git a/jest.config.js b/jest.config.js index e90fad6..3863e5c 100644 --- a/jest.config.js +++ b/jest.config.js @@ -17,4 +17,4 @@ module.exports = { moduleNameMapper: { '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', }, -} \ No newline at end of file +} diff --git a/package.json b/package.json index 4e59425..d4a78b8 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "build": "next build", "start": "next start -p $PORT", "test": "jest", - "lint": "eslint \"pages/**\" \"lib/**\" \"server/**\" \"components/**\" \"tests/**\"", - "lint-fix": "eslint \"pages/**\" \"lib/**\" \"server/**\" \"components/**\" \"tests/**\" --fix", + "lint": "eslint .", + "lint-fix": "eslint . --fix", "heroku-postbuild": "npm run build" }, "dependencies": { diff --git a/postcss.config.js b/postcss.config.js index 45dfbb3..fb6b39c 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -2,4 +2,5 @@ module.exports = { plugins: [ 'tailwindcss', 'postcss-preset-env', - ]} + ], +} diff --git a/setupTests.js b/setupTests.js index dc27ff4..1f93dac 100644 --- a/setupTests.js +++ b/setupTests.js @@ -6,5 +6,5 @@ import '@testing-library/jest-dom/extend-expect' import { config } from 'dotenv' -config({path: './.env.test'}) -config({path: './.env'}) \ No newline at end of file +config({ path: './.env.test' }) +config({ path: './.env' }) diff --git a/tailwind.config.js b/tailwind.config.js index d39fe4e..7af5a9e 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -13,11 +13,11 @@ module.exports = { extend: { fontFamily: { sans: ['Inter var', ...defaultTheme.fontFamily.sans], - } + }, }, }, variants: {}, plugins: [ - require('@tailwindcss/ui') + require('@tailwindcss/ui'), ], } From 1b9e40efd72384663a559c24566cb062a447f0de Mon Sep 17 00:00:00 2001 From: Brent Sanders Date: Tue, 6 Oct 2020 13:53:38 -0500 Subject: [PATCH 14/27] fixes readme typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index daaeabc..eb6581b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![Formulated Logo](https://www.formulatedautomation.com/wp-content/uploads/2020/07/Subtract-660x20-1.svg) -**Formulated Autoamation RPA Resources** +**Formulated Automation RPA Resources** - [/r/OpenSourceRPA](https://reddit.com/r/OpenSourceRPA) From c9069bdeb53b6495f67ccd75c58c6f10eb76cdd0 Mon Sep 17 00:00:00 2001 From: Brent Sanders Date: Tue, 6 Oct 2020 14:00:02 -0500 Subject: [PATCH 15/27] removes vscode --- .gitignore | 2 +- .vscode/settings.json | 26 -------------------------- 2 files changed, 1 insertion(+), 27 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index cda40ce..b2be3d9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ /node_modules /.pnp .pnp.js - +.vscode # testing /coverage diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 9b9d7f7..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,26 +0,0 @@ -/* -👋 Hi! This file was autogenerated by tslint-to-eslint-config. -https://github.com/typescript-eslint/tslint-to-eslint-config - -It represents the closest reasonable ESLint configuration to this -project's original TSLint configuration. - -We recommend eventually switching this configuration to extend from -the recommended rulesets in typescript-eslint. -https://github.com/typescript-eslint/tslint-to-eslint-config/blob/master/docs/FAQs.md - -Happy linting! 💖 -*/ -{ - "cSpell.words": [ - "Jobkey", - "Robocloud", - "botcommander", - "esnext", - "odata", - "robo", - "uipath", - "tailwindcss" - ], - "cSpell.diagnosticLevel": "Hint", -} From 223996305cce4bc80f047c2c3e02f2b5e243b2d5 Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Tue, 6 Oct 2020 19:19:21 +0000 Subject: [PATCH 16/27] Table view of arguments - Turning this over to Brent for making it less ugly, not critical for MVP #21 --- components/InputOutputArgs.tsx | 56 ++++++++++++++++++++++++++++++++++ pages/job/[botId]/[id].tsx | 3 +- server/models/UiPathJob.ts | 6 ++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 components/InputOutputArgs.tsx diff --git a/components/InputOutputArgs.tsx b/components/InputOutputArgs.tsx new file mode 100644 index 0000000..b0579cc --- /dev/null +++ b/components/InputOutputArgs.tsx @@ -0,0 +1,56 @@ +import React, { FunctionComponent } from 'react' +import PropTypes from 'prop-types' + +interface InputArgs { + [key: string]: any +} + +interface OutputArgs { + [key: string]: any +} + +const TBody = ({ obj, keyPrefix }: {obj: object, keyPrefix: string}) => { + return + { + Object.keys(obj).map((key, idx) => { + return ( + + {key} + {obj[key]} + + ) + }) + } + + +} + +const InputOutputArgs: FunctionComponent<{ inputArgs: InputArgs, outputArgs: OutputArgs }> = ({ inputArgs, outputArgs }) => { + return
+ + + + + + + + +
InputNameValue
+ + + + + + + + +
Output NameValue
+
+} + +InputOutputArgs.propTypes = { + inputArgs: PropTypes.array, + outputArgs: PropTypes.array, +} + +export default InputOutputArgs diff --git a/pages/job/[botId]/[id].tsx b/pages/job/[botId]/[id].tsx index e389ecb..bbffd94 100644 --- a/pages/job/[botId]/[id].tsx +++ b/pages/job/[botId]/[id].tsx @@ -6,6 +6,7 @@ import Layout from '../../../components/Layout' import AttachmentTable from '../../../components/AttachmentTable' import Link from 'next/link' import classNames from 'classnames' +import InputOutputArgs from '../../../components/InputOutputArgs' const DynamicReactJson = dynamic(import('react-json-view'), { ssr: false }) @@ -68,7 +69,7 @@ const BotView = ({ jobInfo, botInfo, hostUrl }: AppProps) => { {job.artifacts &&
} - {job.OutputArguments &&
{job.OutputArguments}
} + {job.output && job.output.arguments && }
} {job.output && job.output.arguments && } - +
- {botInfo.type === 'uipath' ? ( + {botInfo.arguments && botInfo.arguments.inputs ? ( - ) : (false)} + ) :
} - {botInfo.type === 'robocloud' ? ( -
- ) : (false)}
@@ -116,6 +113,16 @@ const BotView = ({ botInfo }: AppProps) => {
+
+
+
+ Arguments +
+
+ +
+
+
diff --git a/pages/job/[botId]/[id].tsx b/pages/job/[botId]/[id].tsx index c08dcf5..51c493c 100644 --- a/pages/job/[botId]/[id].tsx +++ b/pages/job/[botId]/[id].tsx @@ -69,7 +69,7 @@ const BotView = ({ jobInfo, botInfo, hostUrl }: AppProps) => { {job.artifacts &&
} - {job.output && job.output.arguments && } + {job.arguments && job.arguments.output && }