├── .github
├── dependabot.yml
└── workflows
│ ├── check-linked-issues.yml
│ ├── ci.yml
│ └── notify-release.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .nvmrc
├── CHANGELOG.md
├── README.md
├── commitlint.config.js
├── docs
├── brokeneck-dark.png
└── brokeneck-light.png
├── examples
└── custom-path
│ ├── .eslintrc
│ ├── .prettierrc
│ ├── CHANGELOG.md
│ ├── index.js
│ └── package.json
├── lerna.json
├── package.json
├── packages
├── brokeneck-desktop
│ ├── .eslintrc
│ ├── .gitignore
│ ├── .prettierrc
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── main.js
│ ├── package.json
│ └── preload.js
├── brokeneck-fastify
│ ├── .env.sample
│ ├── .eslintrc
│ ├── .gitignore
│ ├── .prettierrc
│ ├── .taprc
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── index.js
│ ├── lib
│ │ ├── envConfig.js
│ │ ├── plugin.js
│ │ ├── plugin.test.js
│ │ ├── pluginConfig.js
│ │ └── plugins
│ │ │ ├── auth
│ │ │ ├── auth0
│ │ │ │ ├── index.js
│ │ │ │ ├── index.test.js
│ │ │ │ ├── provider.js
│ │ │ │ ├── provider.test.js
│ │ │ │ └── typeDefs.js
│ │ │ ├── azure
│ │ │ │ ├── index.js
│ │ │ │ ├── index.test.js
│ │ │ │ ├── operations.js
│ │ │ │ ├── parameters.js
│ │ │ │ ├── provider.js
│ │ │ │ ├── provider.test.js
│ │ │ │ └── typeDefs.js
│ │ │ ├── cognito
│ │ │ │ ├── index.js
│ │ │ │ ├── index.test.js
│ │ │ │ ├── provider.js
│ │ │ │ ├── provider.test.js
│ │ │ │ └── typeDefs.js
│ │ │ ├── index.js
│ │ │ └── index.test.js
│ │ │ ├── graphql
│ │ │ ├── index.js
│ │ │ ├── index.test.js
│ │ │ ├── loaders.js
│ │ │ ├── loaders.test.js
│ │ │ ├── resolvers.js
│ │ │ ├── resolvers.test.js
│ │ │ └── typeDefs.js
│ │ │ └── ui
│ │ │ ├── index.js
│ │ │ └── index.test.js
│ └── package.json
├── brokeneck-html
│ ├── .env.sample
│ ├── .eslintignore
│ ├── .eslintrc
│ ├── .gitignore
│ ├── .prettierrc
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── index.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.png
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── scripts
│ │ └── postbuild.js
│ ├── setupTests.js
│ └── src
│ │ ├── index.js
│ │ └── index.test.js
└── brokeneck-react
│ ├── .babelrc
│ ├── .eslintignore
│ ├── .eslintrc
│ ├── .gitignore
│ ├── .prettierrc
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── rollup.config.mjs
│ ├── setupTests.js
│ └── src
│ ├── GraphQLError.js
│ ├── components
│ ├── Admin.js
│ ├── Admin.test.js
│ ├── Entities.js
│ ├── Entity.js
│ ├── EntityFields.js
│ ├── EntityFields.test.js
│ ├── ErrorBoundary.js
│ ├── ErrorBoundary.test.js
│ ├── Footer.js
│ ├── Footer.test.js
│ ├── FormField.js
│ ├── GraphQLErrorBoundary.js
│ ├── GraphQLErrorBoundary.test.js
│ ├── GraphQLErrorContext.js
│ ├── Group.js
│ ├── Group.test.js
│ ├── Groups.js
│ ├── Navigation.js
│ ├── Navigation.test.js
│ ├── Provider.js
│ ├── Provider.test.js
│ ├── RemoveTrailingSlash.js
│ ├── RootContext.js
│ ├── Square.js
│ ├── Square.test.js
│ ├── ThemeSwitcherProvider.js
│ ├── User.js
│ ├── User.test.js
│ └── Users.js
│ ├── graphql.js
│ ├── hooks
│ ├── useAddUserToGroupDialog.js
│ ├── useAddUsersToGroupDialog.js
│ ├── useConfirmDialog.js
│ ├── useCreateGroupDialog.js
│ ├── useCreateUserDialog.js
│ ├── useDialog.js
│ ├── useDialog.test.js
│ ├── useEditEntityDialog.js
│ ├── useFields.js
│ ├── useGraphQLClient.js
│ ├── usePagination.js
│ ├── useProvider.js
│ ├── useProvider.test.js
│ ├── useSchema.js
│ ├── useSchema.test.js
│ └── useSearch.js
│ ├── icons
│ ├── cancel.js
│ ├── check.js
│ ├── cross.js
│ ├── moon.js
│ ├── plus.js
│ ├── providers
│ │ ├── auth0.js
│ │ ├── azure.js
│ │ └── cognito.js
│ ├── refresh.js
│ └── sun.js
│ ├── index.js
│ ├── test-utils
│ ├── mockRootContext.js
│ └── providers.js
│ ├── theme.js
│ └── types.js
└── yarn.lock
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: '/'
5 | schedule:
6 | interval: daily
7 | - package-ecosystem: github-actions
8 | directory: '/'
9 | schedule:
10 | interval: daily
--------------------------------------------------------------------------------
/.github/workflows/check-linked-issues.yml:
--------------------------------------------------------------------------------
1 | name: Check Linked Issues
2 | 'on':
3 | pull_request_target:
4 | types:
5 | - opened
6 | - edited
7 | - reopened
8 | - synchronize
9 | jobs:
10 | check_pull_requests:
11 | runs-on: ubuntu-latest
12 | name: Check linked issues
13 | steps:
14 | - uses: nearform-actions/github-action-check-linked-issues@v1
15 | with:
16 | github-token: ${{ secrets.GITHUB_TOKEN }}
17 | exclude-branches: release/**, dependabot/**
18 | permissions:
19 | issues: read
20 | pull-requests: write
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 | on:
3 | push:
4 | branches:
5 | - master
6 | pull_request:
7 | branches:
8 | - master
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 | - uses: actions/setup-node@v3
15 | with:
16 | node-version-file: .nvmrc
17 | cache: yarn
18 | - run: npx lerna bootstrap
19 | - run: npx lerna run lint
20 | - run: npx lerna run build
21 | - run: npx lerna run test
22 | automerge:
23 | needs: build
24 | runs-on: ubuntu-latest
25 | permissions:
26 | pull-requests: write
27 | contents: write
28 | steps:
29 | - uses: fastify/github-action-merge-dependabot@v3
30 |
--------------------------------------------------------------------------------
/.github/workflows/notify-release.yml:
--------------------------------------------------------------------------------
1 | name: notify-release
2 | on:
3 | workflow_dispatch:
4 | schedule:
5 | - cron: '30 8 * * *'
6 | release:
7 | types: [published]
8 | issues:
9 | types: [closed]
10 | jobs:
11 | setup:
12 | runs-on: ubuntu-latest
13 | permissions:
14 | issues: write
15 | steps:
16 | - name: Notify release
17 | uses: nearform-actions/github-action-notify-release@v1
18 | with:
19 | github-token: ${{ secrets.GITHUB_TOKEN }}
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | *.log
3 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | yarn commitlint --edit "$1"
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | yarn pre-commit && yarn toc && git add README.md
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/*
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | # [1.0.0-spinal.8](https://github.com/nearform/brokeneck/compare/v1.0.0-spinal.7...v1.0.0-spinal.8) (2021-01-06)
7 |
8 |
9 | ### Bug Fixes
10 |
11 | * fix peerDependencies with material ui lab ([#41](https://github.com/nearform/brokeneck/issues/41)) ([dadfe56](https://github.com/nearform/brokeneck/commit/dadfe5669bf1af3c70018e329ce6d5d51197aa18))
12 | * **deps:** update dependency token-pagination-hooks to ^0.2.0 ([#35](https://github.com/nearform/brokeneck/issues/35)) ([3fa5f34](https://github.com/nearform/brokeneck/commit/3fa5f34915751350d3092ed69945be54a256a079))
13 |
14 |
15 | ### Features
16 |
17 | * use token-pagination-hooks library ([78152d9](https://github.com/nearform/brokeneck/commit/78152d9e636c9419591d60071f201b199becae04)), closes [#21](https://github.com/nearform/brokeneck/issues/21)
18 |
19 |
20 |
21 |
22 |
23 | # [1.0.0-spinal.7](https://github.com/nearform/brokeneck/compare/v1.0.0-spinal.6...v1.0.0-spinal.7) (2020-12-30)
24 |
25 |
26 | ### Features
27 |
28 | * field formatting ([7e5fd91](https://github.com/nearform/brokeneck/commit/7e5fd91679a50e968a13b171b26dc99474aaafa6))
29 | * simple form validation ([2c839d0](https://github.com/nearform/brokeneck/commit/2c839d00d1188737c876994ce7fc1cd5858bc9a3))
30 |
31 |
32 |
33 |
34 |
35 | # 1.0.0-spinal.6 (2020-12-29)
36 |
37 |
38 | ### Bug Fixes
39 |
40 | * missing package ([992d070](https://github.com/nearform/brokeneck/commit/992d07044d36658543a237d5bd525d0e90c057bb))
41 | * **deps:** update dependency fastify-cors to v5 ([#9](https://github.com/nearform/brokeneck/issues/9)) ([dc3513b](https://github.com/nearform/brokeneck/commit/dc3513b329bc832e7826af8672d4a39e9c36d177))
42 | * fix checkbox handling ([760e289](https://github.com/nearform/brokeneck/commit/760e289073a32bd7b3b2b08e330e37b34fb56239))
43 | * handle non object config ([bbb0538](https://github.com/nearform/brokeneck/commit/bbb0538f97ef1b9f6509870851e5b59256b7b2fc))
44 |
45 |
46 | ### Features
47 |
48 | * auth0 group search functionality ([#20](https://github.com/nearform/brokeneck/issues/20)) ([406f42e](https://github.com/nearform/brokeneck/commit/406f42e89224bfb9ba1f5d71c3068c5ac6f40656))
49 | * dialog cancel button ([f842ada](https://github.com/nearform/brokeneck/commit/f842ada6e85508cf2fc87a364078f501109b4106))
50 | * pagination ([#12](https://github.com/nearform/brokeneck/issues/12)) ([c3965ba](https://github.com/nearform/brokeneck/commit/c3965ba370291d9d4f3dc80e31b57ecd6742fbe4))
51 | * pagination groups ([#18](https://github.com/nearform/brokeneck/issues/18)) ([ac4dfe5](https://github.com/nearform/brokeneck/commit/ac4dfe598b5205394f6c2ab181901582dea311ed))
52 | * pagination groups users ([#24](https://github.com/nearform/brokeneck/issues/24)) ([4e9a463](https://github.com/nearform/brokeneck/commit/4e9a463f7b8c98e8629c4176f35bb95e8a8a080a))
53 | * support dates and add more cognito fields ([2e1dcdc](https://github.com/nearform/brokeneck/commit/2e1dcdc628beb0644d241c5c6ece638b1e901d68))
54 | * **capabilities:** group search ([#31](https://github.com/nearform/brokeneck/issues/31)) ([c201012](https://github.com/nearform/brokeneck/commit/c2010129fef23176da021b784261ec046f5cf4c6))
55 | * prerelase spinal ([fd89f52](https://github.com/nearform/brokeneck/commit/fd89f523ad65d1b797fe3b35b2912bd264d80860))
56 | * support fastify prefix subset ([#16](https://github.com/nearform/brokeneck/issues/16)) ([4c67b31](https://github.com/nearform/brokeneck/commit/4c67b316613d8fa3d7126a644f411910d9e1540a))
57 | * use startCase to show type fields ([29bd2e4](https://github.com/nearform/brokeneck/commit/29bd2e44e16e3b31cc088caf6cbc08f5d1a49e3b))
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # codename brokeneck
2 |
3 | [](https://github.com/nearform/brokeneck/actions?query=workflow%3Aci)
4 | [](https://lerna.js.org/)
5 | [](https://conventionalcommits.org)
6 |
7 | This monorepo contains packages and applications which provide a way to manage users and groups in:
8 |
9 | - Auth0
10 | - AWS Cognito
11 | - Azure AD
12 |
13 |
14 |
15 |
16 |
17 | - [Getting started](#getting-started)
18 | - [Development](#development)
19 | - [Providers](#providers)
20 | * [Auth0](#auth0)
21 | * [AWS Cognito](#aws-cognito)
22 | * [Azure AD](#azure-ad)
23 | - [Packages](#packages)
24 | * [`brokeneck-react`](#brokeneck-react)
25 | * [`brokeneck-html`](#brokeneck-html)
26 | * [`brokeneck-fastify`](#brokeneck-fastify)
27 | * [`brokeneck-desktop`](#brokeneck-desktop)
28 | - [Usage](#usage)
29 | * [Fully standalone](#fully-standalone)
30 | * [Separate Web frontend and backend](#separate-web-frontend-and-backend)
31 | * [As a React component](#as-a-react-component)
32 |
33 |
34 |
35 | ## Getting started
36 |
37 | You'll need yarn and lerna installed globally:
38 |
39 | - `npm i -g yarn lerna`
40 |
41 | The easiest way to try this out is to run the standalone server via `brokeneck-fastify`:
42 |
43 | - `lerna bootstrap`
44 | - `lerna run build`
45 | - `lerna run create:env`
46 | - configure `packages/brokeneck-fastify/.env` based on the authentication provider you want to use
47 | - `packages/brokeneck-html/.env` will be preconfigured with the correct REACT_APP_API_PATH for local development.
48 | - `yarn start`
49 | - browse to [`http://localhost:5001`](http://localhost:5001)
50 |
51 | ## Development
52 |
53 | To easily develop the packages of this repo you can execute:
54 |
55 | ```sh
56 | yarn dev
57 | ```
58 |
59 | This will run:
60 |
61 | - `brokeneck-react` build in watch mode so you can change the React components and see the result immediately
62 | - `brokeneck-html` in standalone mode to have a Web UI to access
63 | - `brokeneck-fastify` in standalone mode to have a server running
64 |
65 | ## Providers
66 |
67 | Each provider requires its own configuration, as specified in `brokeneck-fastify`'s `.env.sample` file.
68 |
69 | Such configuration can be obtained when configuring the respective service using the provider's usual tooling.
70 |
71 | There are additional requirements based on the provider, described in the next sections.
72 |
73 | ### Auth0
74 |
75 | The configured client should be authorized to access the built-in `Auth0 Management API`.
76 |
77 | ### AWS Cognito
78 |
79 | When using AWS cognito you must make sure that you have IAM credentials configured for the SDK in the machine running the application, for instance in the `.aws/credentials` file.
80 |
81 | The configured IAM user must have access to Cognito. The simplest way to do this is to add the `AmazonCognitoPowerUser` _AWS managed policy_ to the user.
82 |
83 | ### Azure AD
84 |
85 | The registered application must have all the necessary `Application permissions` (not `Delegated permissions`) to operate on the relevant objects.
86 |
87 | These basically include all the combinations of the permissions:
88 |
89 | - API: `Azure Active Directory Graph` and `Microsoft Graph`
90 | - Objects: `Directory`, `Users`, `Groups` and `GroupMember`
91 | - Permissions: `Read.All`, `ReadWrite.All`, `Create`,
92 |
93 | Not all combinations exists, but you should enabled them when they do.
94 |
95 | ## Packages
96 |
97 | ### `brokeneck-react`
98 |
99 | A library of React components which provide the UI.
100 |
101 | ### `brokeneck-html`
102 |
103 | This package comes in 2 flavors:
104 |
105 | - a standalone CRA application running the UI
106 | - static assets containig the whole UI, which can be rendered by an external application
107 |
108 | ### `brokeneck-fastify`
109 |
110 | This package comes in two flavors:
111 |
112 | - a standalone Fastify application
113 | - a Fastify plugin which can be used by an external application
114 |
115 | ### `brokeneck-desktop`
116 |
117 | An Electron application which runs `brokeneck-fastify` with the embedded UI.
118 |
119 | ## Usage
120 |
121 | The packages work in synergy to support different deployment and usage scenarios, described next from the simplest to the most complex and flexible.
122 |
123 | > The important thing to keep in mind is that this application requires both a frontend and a backend.
124 |
125 | ### Fully standalone
126 |
127 | If you want to run the application standalone you can use:
128 |
129 | - `brokeneck-fastify` in standalone mode and make it render the UI by configuring the application via environment variables
130 | - `brokeneck-desktop` to run the application in desktop mode
131 |
132 | ### Separate Web frontend and backend
133 |
134 | You can run `brokeneck-fastify` in standalone mode without rendering the UI and `brokeneck-html` in standalone mode to provide the UI.
135 |
136 | They will be two independent applications.
137 |
138 | ### As a React component
139 |
140 | You can install `brokeneck-react` in your React application and use the exported component.
141 |
142 | For example, you can provide it as an additional route inside your application.
143 |
144 | brokeneck-react has a number of peer dependencies that you have to add to your application:
145 |
146 | `npm install @material-ui/core @material-ui/lab react react-dom react-router-dom`
147 |
148 | Please check the exact versions of these packages that are required when installing brokeneck-react.
149 |
150 | You will still need to provide a backend, which you can do either by:
151 |
152 | - running `brokeneck-fastify` in standalone mode
153 | - using `brokeneck-fastify` as a plugin inside an existing Fastify application
154 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = { extends: ["@commitlint/config-conventional"] };
2 |
--------------------------------------------------------------------------------
/docs/brokeneck-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nearform/brokeneck/9078f9595c5c8f53b52ee98a156c6736dc20dbcd/docs/brokeneck-dark.png
--------------------------------------------------------------------------------
/docs/brokeneck-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nearform/brokeneck/9078f9595c5c8f53b52ee98a156c6736dc20dbcd/docs/brokeneck-light.png
--------------------------------------------------------------------------------
/examples/custom-path/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["standard", "plugin:prettier/recommended"],
3 | "env": {
4 | "node": true
5 | },
6 | "parserOptions": {
7 | "sourceType": "script"
8 | },
9 | "rules": {
10 | "strict": ["error", "global"],
11 | "import/order": [
12 | "error",
13 | {
14 | "newlines-between": "always"
15 | }
16 | ]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/custom-path/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "singleQuote": true,
4 | "trailingComma": "none",
5 | "semi": false
6 | }
7 |
--------------------------------------------------------------------------------
/examples/custom-path/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | # [1.1.0](https://github.com/nearform/brokeneck/compare/custom-path@1.0.1...custom-path@1.1.0) (2020-12-29)
7 |
8 |
9 | ### Features
10 |
11 | * support fastify prefix subset ([#16](https://github.com/nearform/brokeneck/issues/16)) ([4c67b31](https://github.com/nearform/brokeneck/commit/4c67b316613d8fa3d7126a644f411910d9e1540a))
12 |
13 |
14 |
15 |
16 |
17 | ## 1.0.1 (2020-12-05)
18 |
19 | **Note:** Version bump only for package custom-path
20 |
21 |
22 |
23 |
24 |
25 | ## 1.0.1 (2020-12-05)
26 |
27 | **Note:** Version bump only for package custom-path
28 |
--------------------------------------------------------------------------------
/examples/custom-path/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 |
5 | const pkgDir = require('pkg-dir')
6 | const brokeneck = require('@nearform/brokeneck-fastify')
7 |
8 | async function customPath(fastify, options) {
9 | const envPath = path.join(
10 | pkgDir.sync(require.resolve('@nearform/brokeneck-fastify')),
11 | '.env'
12 | )
13 |
14 | require('dotenv').config({ path: envPath })
15 |
16 | fastify.register(brokeneck, { prefix: '/admin' })
17 |
18 | fastify.get('/', (req, reply) =>
19 | reply.header('Content-Type', 'text/html').send(`
20 |
21 |
You'll find the admin UI here
35 | 36 | `) 37 | ) 38 | } 39 | 40 | module.exports = customPath 41 | -------------------------------------------------------------------------------- /examples/custom-path/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-path", 3 | "version": "1.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "fastify start -w -P -l debug -p 4001 .", 7 | "lint": "eslint .", 8 | "lint:fix": "eslint . --fix", 9 | "precommit": "lint-staged" 10 | }, 11 | "devDependencies": { 12 | "dotenv": "^16.0.0", 13 | "fastify-cli": "^5.0.0", 14 | "pkg-dir": "^5.0.0" 15 | }, 16 | "lint-staged": { 17 | "**/*.js": [ 18 | "eslint --fix" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "useWorkspaces": true, 4 | "version": "1.0.0-spinal.8", 5 | "command": { 6 | "version": { 7 | "allowBranch": "master", 8 | "message": "chore(release): 🚀 publish %s", 9 | "conventionalCommits": true 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nearform/brokeneck", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*", 6 | "examples/*" 7 | ], 8 | "repository": "nearform/brokeneck", 9 | "author": { 10 | "name": "Simone Busoli", 11 | "email": "simone.busoli@nearform.com" 12 | }, 13 | "scripts": { 14 | "dev": "lerna run dev --parallel", 15 | "test": "lerna run test --stream", 16 | "version:safe": "lerna version --no-git-tag-version --no-push", 17 | "pre-commit": "lerna run --concurrency 1 --stream precommit --since HEAD --exclude-dependents", 18 | "toc": "markdown-toc README.md -i", 19 | "prepare": "husky install" 20 | }, 21 | "devDependencies": { 22 | "@commitlint/cli": "^17.0.0", 23 | "@commitlint/config-conventional": "^17.0.0", 24 | "husky": "^8.0.1", 25 | "lerna": "^6.0.0", 26 | "markdown-toc": "^1.2.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/brokeneck-desktop/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["standard", "plugin:prettier/recommended"], 3 | "env": { 4 | "node": true 5 | }, 6 | "parserOptions": { 7 | "sourceType": "script" 8 | }, 9 | "rules": { 10 | "strict": ["error", "global"], 11 | "import/order": [ 12 | "error", 13 | { 14 | "newlines-between": "always" 15 | } 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/brokeneck-desktop/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # postgres data files 64 | pgdata 65 | 66 | # gcp service account key 67 | key.json 68 | 69 | .env -------------------------------------------------------------------------------- /packages/brokeneck-desktop/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /packages/brokeneck-desktop/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [1.0.0-spinal.8](https://github.com/nearform/brokeneck/compare/v1.0.0-spinal.7...v1.0.0-spinal.8) (2021-01-06) 7 | 8 | **Note:** Version bump only for package @nearform/brokeneck-desktop 9 | 10 | 11 | 12 | 13 | 14 | # [1.0.0-spinal.7](https://github.com/nearform/brokeneck/compare/v1.0.0-spinal.6...v1.0.0-spinal.7) (2020-12-30) 15 | 16 | **Note:** Version bump only for package @nearform/brokeneck-desktop 17 | 18 | 19 | 20 | 21 | 22 | # 1.0.0-spinal.6 (2020-12-29) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * fix checkbox handling ([760e289](https://github.com/nearform/brokeneck/commit/760e289073a32bd7b3b2b08e330e37b34fb56239)) 28 | 29 | 30 | ### Features 31 | 32 | * support fastify prefix subset ([#16](https://github.com/nearform/brokeneck/issues/16)) ([4c67b31](https://github.com/nearform/brokeneck/commit/4c67b316613d8fa3d7126a644f411910d9e1540a)) 33 | 34 | 35 | 36 | 37 | 38 | # [1.0.0-spinal.5](https://github.com/nearform/brokeneck/compare/@nearform/brokeneck-desktop@1.0.0-spinal.4...@nearform/brokeneck-desktop@1.0.0-spinal.5) (2020-12-29) 39 | 40 | **Note:** Version bump only for package @nearform/brokeneck-desktop 41 | 42 | 43 | 44 | 45 | 46 | # [1.0.0-spinal.4](https://github.com/nearform/brokeneck/compare/@nearform/brokeneck-desktop@1.0.0-spinal.3...@nearform/brokeneck-desktop@1.0.0-spinal.4) (2020-12-29) 47 | 48 | 49 | ### Features 50 | 51 | * support fastify prefix subset ([#16](https://github.com/nearform/brokeneck/issues/16)) ([4c67b31](https://github.com/nearform/brokeneck/commit/4c67b316613d8fa3d7126a644f411910d9e1540a)) 52 | 53 | 54 | 55 | 56 | 57 | # [1.0.0-spinal.3](https://github.com/nearform/brokeneck/compare/@nearform/brokeneck-desktop@1.0.0-spinal.2...@nearform/brokeneck-desktop@1.0.0-spinal.3) (2020-12-09) 58 | 59 | **Note:** Version bump only for package @nearform/brokeneck-desktop 60 | 61 | 62 | 63 | 64 | 65 | # [1.0.0-spinal.2](https://github.com/nearform/brokeneck/compare/@nearform/brokeneck-desktop@1.0.0-spinal.1...@nearform/brokeneck-desktop@1.0.0-spinal.2) (2020-12-05) 66 | 67 | **Note:** Version bump only for package @nearform/brokeneck-desktop 68 | 69 | 70 | 71 | 72 | 73 | # 1.0.0-spinal.1 (2020-12-05) 74 | 75 | 76 | ### Bug Fixes 77 | 78 | * fix checkbox handling ([760e289](https://github.com/nearform/brokeneck/commit/760e289073a32bd7b3b2b08e330e37b34fb56239)) 79 | 80 | 81 | 82 | 83 | 84 | # 1.0.0-spinal.0 (2020-12-03) 85 | 86 | **Note:** Version bump only for package brokeneck-desktop 87 | -------------------------------------------------------------------------------- /packages/brokeneck-desktop/README.md: -------------------------------------------------------------------------------- 1 | # brokeneck-desktop 2 | 3 | Runs your Admin UI as a desktop application! 4 | 5 | ## How to use it 6 | 7 | 1. Make sure you built the UI at least once: run `lerna run build` at root level 8 | 1. Configure brockneck-fastify to serve UI. `packages/brokeneck-fastify/.env` must include `BROKENECK_UI=true` 9 | 1. Start electron: `yarn start` 10 | 11 | ## How it works 12 | 13 | `brokeneck-desktop` is an Electron wrapper which starts your `brokeneck-fastify` server, and opens it as an HTML page. 14 | It behaves exactly the same as if you were browsing the Admin UI with Chromium browser. The only difference is your server running _within_ the desktop application instead of being hosted and accessed through the wire. 15 | 16 | It still requires connectivity to access the actual Authentication provider (Auth0, Azure Active Directory or AWS Cognito). 17 | 18 | Anything that UI would store in its local-storage is persisted when you close the application. 19 | 20 | ## Development 21 | 22 | Development should happen on [brokeneck-fastify](../brokeneck-fastify/README.md) & [brokeneck-react](../brokeneck-react/README.md) packages only. 23 | If you change brokeneck server or UI, rebuild them with the `lerna run build` command at top level, and restart the desktop application with `yarn start`. -------------------------------------------------------------------------------- /packages/brokeneck-desktop/main.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | 5 | const { app, BrowserWindow } = require('electron') 6 | const Fastify = require('fastify') 7 | const brokeneck = require('@nearform/brokeneck-fastify') 8 | const pkgDir = require('pkg-dir') 9 | const dotenv = require('dotenv') 10 | const { setContentSecurityPolicy } = require('electron-util') 11 | const Store = require('electron-store') 12 | 13 | const store = new Store() 14 | 15 | async function start() { 16 | const address = await startServer() 17 | 18 | await app.whenReady() 19 | 20 | createWindow(address) 21 | } 22 | 23 | async function startServer() { 24 | const envPath = path.join( 25 | pkgDir.sync(require.resolve('@nearform/brokeneck-fastify')), 26 | '.env' 27 | ) 28 | dotenv.config({ path: envPath }) 29 | 30 | const fastify = Fastify().register(brokeneck) 31 | 32 | const address = await fastify.listen() 33 | 34 | await fastify.ready() 35 | 36 | return address 37 | } 38 | 39 | async function createWindow(address) { 40 | const win = new BrowserWindow({ 41 | width: 800, 42 | height: 600, 43 | autoHideMenuBar: true, 44 | webPreferences: { 45 | enableRemoteModule: false, 46 | contextIsolation: true, 47 | preload: path.join(app.getAppPath(), 'preload.js'), 48 | additionalArguments: [JSON.stringify(store.store)] 49 | } 50 | }) 51 | 52 | win.loadURL(address) 53 | 54 | win.on('close', async () => { 55 | const localStorage = await win.webContents.executeJavaScript( 56 | 'JSON.stringify(window.localStorage)' 57 | ) 58 | 59 | store.store = JSON.parse(localStorage) 60 | }) 61 | 62 | setContentSecurityPolicy(` 63 | script-src 'self' 'unsafe-inline' 'unsafe-eval'; 64 | `) 65 | } 66 | 67 | app.on('window-all-closed', () => { 68 | if (process.platform !== 'darwin') { 69 | app.quit() 70 | } 71 | }) 72 | 73 | start() 74 | -------------------------------------------------------------------------------- /packages/brokeneck-desktop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nearform/brokeneck-desktop", 3 | "version": "1.0.0-spinal.8", 4 | "author": { 5 | "name": "Simone Busoli", 6 | "email": "simone.busoli@nearform.com" 7 | }, 8 | "repository": "nearform/brokeneck", 9 | "main": "main.js", 10 | "scripts": { 11 | "start": "electron .", 12 | "lint": "eslint .", 13 | "lint:fix": "eslint . --fix", 14 | "precommit": "lint-staged" 15 | }, 16 | "dependencies": { 17 | "@nearform/brokeneck-fastify": "^1.0.0-spinal.8", 18 | "dotenv": "^16.0.0", 19 | "electron-store": "^8.0.0", 20 | "electron-util": "^0.17.0", 21 | "fastify": "^4.5.3", 22 | "pkg-dir": "^5.0.0" 23 | }, 24 | "devDependencies": { 25 | "electron": "^24.0.0", 26 | "eslint": "^7.14.0", 27 | "eslint-config-prettier": "^8.0.0", 28 | "eslint-config-standard": "^16.0.2", 29 | "eslint-plugin-prettier": "^4.0.0", 30 | "lint-staged": "^13.0.0", 31 | "prettier": "^2.2.1" 32 | }, 33 | "lint-staged": { 34 | "**/*.js": [ 35 | "eslint --fix" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/brokeneck-desktop/preload.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const store = JSON.parse(process.argv.slice(-1)) 4 | 5 | console.log('Restoring localStorage from store') 6 | console.dir(store) 7 | 8 | Object.entries(store).forEach(([key, value]) => 9 | window.localStorage.setItem(key, value) 10 | ) 11 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/.env.sample: -------------------------------------------------------------------------------- 1 | CORS_ORIGIN=true 2 | 3 | BROKENECK_UI=true 4 | BROKENECK_PROVIDER=auth0|cognito|azure 5 | 6 | BROKENECK_AUTH0_DOMAIN= 7 | BROKENECK_AUTH0_CLIENT_ID= 8 | BROKENECK_AUTH0_CLIENT_SECRET= 9 | BROKENECK_AUTH0_CONNECTION= 10 | 11 | BROKENECK_AZURE_TENANT_ID= 12 | BROKENECK_AZURE_CLIENT_ID= 13 | BROKENECK_AZURE_SECRET= 14 | 15 | BROKENECK_COGNITO_USER_POOL_ID= 16 | BROKENECK_COGNITO_REGION= 17 | 18 | BROKENECK_MERCURIUS_GRAPHIQL= 19 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["standard", "plugin:prettier/recommended"], 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "sourceType": "script" 9 | }, 10 | "rules": { 11 | "no-console": "error", 12 | "strict": ["error", "global"], 13 | "import/order": [ 14 | "error", 15 | { 16 | "newlines-between": "always" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # postgres data files 64 | pgdata 65 | 66 | # gcp service account key 67 | key.json 68 | 69 | .env -------------------------------------------------------------------------------- /packages/brokeneck-fastify/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/.taprc: -------------------------------------------------------------------------------- 1 | 100: true 2 | reporter: spec -------------------------------------------------------------------------------- /packages/brokeneck-fastify/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [1.0.0-spinal.8](https://github.com/nearform/brokeneck/compare/v1.0.0-spinal.7...v1.0.0-spinal.8) (2021-01-06) 7 | 8 | **Note:** Version bump only for package @nearform/brokeneck-fastify 9 | 10 | 11 | 12 | 13 | 14 | # [1.0.0-spinal.7](https://github.com/nearform/brokeneck/compare/v1.0.0-spinal.6...v1.0.0-spinal.7) (2020-12-30) 15 | 16 | 17 | ### Features 18 | 19 | * field formatting ([7e5fd91](https://github.com/nearform/brokeneck/commit/7e5fd91679a50e968a13b171b26dc99474aaafa6)) 20 | 21 | 22 | 23 | 24 | 25 | # 1.0.0-spinal.6 (2020-12-29) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * **deps:** update dependency fastify-cors to v5 ([#9](https://github.com/nearform/brokeneck/issues/9)) ([dc3513b](https://github.com/nearform/brokeneck/commit/dc3513b329bc832e7826af8672d4a39e9c36d177)) 31 | * fix checkbox handling ([760e289](https://github.com/nearform/brokeneck/commit/760e289073a32bd7b3b2b08e330e37b34fb56239)) 32 | * handle non object config ([bbb0538](https://github.com/nearform/brokeneck/commit/bbb0538f97ef1b9f6509870851e5b59256b7b2fc)) 33 | 34 | 35 | ### Features 36 | 37 | * auth0 group search functionality ([#20](https://github.com/nearform/brokeneck/issues/20)) ([406f42e](https://github.com/nearform/brokeneck/commit/406f42e89224bfb9ba1f5d71c3068c5ac6f40656)) 38 | * dialog cancel button ([f842ada](https://github.com/nearform/brokeneck/commit/f842ada6e85508cf2fc87a364078f501109b4106)) 39 | * pagination ([#12](https://github.com/nearform/brokeneck/issues/12)) ([c3965ba](https://github.com/nearform/brokeneck/commit/c3965ba370291d9d4f3dc80e31b57ecd6742fbe4)) 40 | * pagination groups ([#18](https://github.com/nearform/brokeneck/issues/18)) ([ac4dfe5](https://github.com/nearform/brokeneck/commit/ac4dfe598b5205394f6c2ab181901582dea311ed)) 41 | * pagination groups users ([#24](https://github.com/nearform/brokeneck/issues/24)) ([4e9a463](https://github.com/nearform/brokeneck/commit/4e9a463f7b8c98e8629c4176f35bb95e8a8a080a)) 42 | * support dates and add more cognito fields ([2e1dcdc](https://github.com/nearform/brokeneck/commit/2e1dcdc628beb0644d241c5c6ece638b1e901d68)) 43 | * **capabilities:** group search ([#31](https://github.com/nearform/brokeneck/issues/31)) ([c201012](https://github.com/nearform/brokeneck/commit/c2010129fef23176da021b784261ec046f5cf4c6)) 44 | * prerelase spinal ([fd89f52](https://github.com/nearform/brokeneck/commit/fd89f523ad65d1b797fe3b35b2912bd264d80860)) 45 | * support fastify prefix subset ([#16](https://github.com/nearform/brokeneck/issues/16)) ([4c67b31](https://github.com/nearform/brokeneck/commit/4c67b316613d8fa3d7126a644f411910d9e1540a)) 46 | 47 | 48 | 49 | 50 | 51 | # [1.0.0-spinal.5](https://github.com/nearform/brokeneck/compare/@nearform/brokeneck-fastify@1.0.0-spinal.4...@nearform/brokeneck-fastify@1.0.0-spinal.5) (2020-12-29) 52 | 53 | **Note:** Version bump only for package @nearform/brokeneck-fastify 54 | 55 | 56 | 57 | 58 | 59 | # [1.0.0-spinal.4](https://github.com/nearform/brokeneck/compare/@nearform/brokeneck-fastify@1.0.0-spinal.3...@nearform/brokeneck-fastify@1.0.0-spinal.4) (2020-12-29) 60 | 61 | 62 | ### Features 63 | 64 | * dialog cancel button ([f842ada](https://github.com/nearform/brokeneck/commit/f842ada6e85508cf2fc87a364078f501109b4106)) 65 | * **capabilities:** group search ([#31](https://github.com/nearform/brokeneck/issues/31)) ([c201012](https://github.com/nearform/brokeneck/commit/c2010129fef23176da021b784261ec046f5cf4c6)) 66 | * auth0 group search functionality ([#20](https://github.com/nearform/brokeneck/issues/20)) ([406f42e](https://github.com/nearform/brokeneck/commit/406f42e89224bfb9ba1f5d71c3068c5ac6f40656)) 67 | * pagination ([#12](https://github.com/nearform/brokeneck/issues/12)) ([c3965ba](https://github.com/nearform/brokeneck/commit/c3965ba370291d9d4f3dc80e31b57ecd6742fbe4)) 68 | * pagination groups ([#18](https://github.com/nearform/brokeneck/issues/18)) ([ac4dfe5](https://github.com/nearform/brokeneck/commit/ac4dfe598b5205394f6c2ab181901582dea311ed)) 69 | * pagination groups users ([#24](https://github.com/nearform/brokeneck/issues/24)) ([4e9a463](https://github.com/nearform/brokeneck/commit/4e9a463f7b8c98e8629c4176f35bb95e8a8a080a)) 70 | * support fastify prefix subset ([#16](https://github.com/nearform/brokeneck/issues/16)) ([4c67b31](https://github.com/nearform/brokeneck/commit/4c67b316613d8fa3d7126a644f411910d9e1540a)) 71 | 72 | 73 | 74 | 75 | 76 | # [1.0.0-spinal.3](https://github.com/nearform/brokeneck/compare/@nearform/brokeneck-fastify@1.0.0-spinal.2...@nearform/brokeneck-fastify@1.0.0-spinal.3) (2020-12-09) 77 | 78 | 79 | ### Bug Fixes 80 | 81 | * **deps:** update dependency fastify-cors to v5 ([#9](https://github.com/nearform/brokeneck/issues/9)) ([dc3513b](https://github.com/nearform/brokeneck/commit/dc3513b329bc832e7826af8672d4a39e9c36d177)) 82 | 83 | 84 | 85 | 86 | 87 | # [1.0.0-spinal.2](https://github.com/nearform/brokeneck/compare/@nearform/brokeneck-fastify@1.0.0-spinal.1...@nearform/brokeneck-fastify@1.0.0-spinal.2) (2020-12-05) 88 | 89 | **Note:** Version bump only for package @nearform/brokeneck-fastify 90 | 91 | 92 | 93 | 94 | 95 | # 1.0.0-spinal.1 (2020-12-05) 96 | 97 | 98 | ### Bug Fixes 99 | 100 | * fix checkbox handling ([760e289](https://github.com/nearform/brokeneck/commit/760e289073a32bd7b3b2b08e330e37b34fb56239)) 101 | * handle non object config ([bbb0538](https://github.com/nearform/brokeneck/commit/bbb0538f97ef1b9f6509870851e5b59256b7b2fc)) 102 | 103 | 104 | ### Features 105 | 106 | * prerelase spinal ([fd89f52](https://github.com/nearform/brokeneck/commit/fd89f523ad65d1b797fe3b35b2912bd264d80860)) 107 | 108 | 109 | 110 | 111 | 112 | # 1.0.0-spinal.0 (2020-12-03) 113 | 114 | 115 | ### Features 116 | 117 | * prerelase spinal ([fd89f52](https://github.com/nearform/brokeneck/commit/fd89f523ad65d1b797fe3b35b2912bd264d80860)) 118 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/README.md: -------------------------------------------------------------------------------- 1 | # brokeneck-fastify 2 | 3 | Plugs into your favourite Authentication provider (Auth0, Azure Active Directory or AWS Cognito) and exposes GraphQL endpoints for managing it. 4 | 5 | It can also serve your Admin UI, or be embedded without your fastify server. 6 | 7 | ## Auth0 setup 8 | 9 | TODO 10 | 11 | ## Azure Active Directory setup 12 | 13 | TODO 14 | 15 | ### AWS Cognito setup 16 | 17 | TODO 18 | 19 | ## How to use it (standalone mode) 20 | 21 | 1. Make sure you built the UI at least once: run `lerna run build` at root level 22 | 1. Plug into an Authentication provider (see Configuration section down bellow) 23 | 1. Start: `yarn start` 24 | 25 | ### Configuration 26 | 27 | Configure your server through environment variables. 28 | For simpler developement, you can use a `.env` file. 29 | 30 | - `CORS_ORIGIN` (boolean): enables the [Access-Control-Allow-Origin header](https://www.npmjs.com/package/fastify-cors#options). Set to true to let your UI accessing the server when running in dev mode. 31 | 32 | - `BROKENECK_UI` (boolean): enables serving Brokeneck UI. 33 | 34 | - `BROKENECK_MERCURIUS_GRAPHIQL` (graphiql|playground): when set, enables serving GraphIQL on http://localhost:5001/graphiqul, or GraphQL playground on http://localhost:5001/playground ([Reference](https://github.com/mercurius-js/mercurius/blob/HEAD/docs/api/options.md#plugin-options)). 35 | 36 | - `BROKENECK_PROVIDER` (auth0|azure|cognito): sets the Authentication provider (required). 37 | 38 | - `BROKENECK_AUTH0_DOMAIN` (string): the Auth0 domain to connect to. Please follow [the instructions](https://www.npmjs.com/package/auth0#management-api-client) on how to allow your brokeneck server accessing Auth0 Management API. 39 | 40 | - `BROKENECK_AUTH0_CLIENT_ID` (string): the Auth0 client ID this server will use. 41 | 42 | - `BROKENECK_AUTH0_CLIENT_SECRET` (string): the Auth0 client secret needed for this server to connect. 43 | 44 | - `BROKENECK_AUTH0_CONNECTION` (string): the name of the [Auth0 connection](https://auth0.com/docs/identityproviders) to use when creating new users. `Username-Password-Authentication` is the name of Auth0 default database created for you. 45 | 46 | - `BROKENECK_AZURE_TENANT_ID` (string): the Azure Active Directory Tenant Id. Create a [tenant](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Overview) and 47 | 48 | - `BROKENECK_AZURE_CLIENT_ID` (string): the Azure Active Directory client ID this server will use. Register an "App" in your tenant to get your client ID. 49 | 50 | - `BROKENECK_AZURE_SECRET` (string): the Azure Active Directory secret needed for this server to connect. Go to your "App" secrets to create one, and use it. 51 | 52 | - `BROKENECK_COGNITO_REGION` (string): the AWS region hosting your Cognito User Pool. 53 | 54 | - `BROKENECK_COGNITO_USER_POOL_ID` (string): the AWS Cognito User Pool ID to create user into. 55 | 56 | 57 | ## How to use it (embedded mode) 58 | 59 | `brokeneck-fastify` can be used as a fastify plugin: 60 | 61 | 62 | 63 | ### Configuration 64 | 65 | TODO 66 | 67 | ## How it works 68 | 69 | `brokeneck-fastify` TODO 70 | 71 | 72 | ## Development 73 | 74 | Development should happen on [brokeneck-fastify](../brokeneck-fastify/README.md) & [brokeneck-react](../brokeneck-react/README.md) packages only. 75 | If you change brokeneck server or UI, rebuild them with the `lerna run build` command at top level, and restart the desktop application with `yarn start`. -------------------------------------------------------------------------------- /packages/brokeneck-fastify/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const cors = require('@fastify/cors') 4 | 5 | const plugin = require('./lib/plugin') 6 | 7 | module.exports = async function (fastify, options) { 8 | fastify.register(cors, { 9 | origin: !!process.env.CORS_ORIGIN, 10 | credentials: true 11 | }) 12 | fastify.register(plugin) 13 | } 14 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/envConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function envConfig() { 4 | const provider = process.env.BROKENECK_PROVIDER 5 | 6 | return { 7 | ui: process.env.BROKENECK_UI, 8 | provider, 9 | ...(provider === 'auth0' && { 10 | auth0: { 11 | domain: process.env.BROKENECK_AUTH0_DOMAIN, 12 | clientId: process.env.BROKENECK_AUTH0_CLIENT_ID, 13 | clientSecret: process.env.BROKENECK_AUTH0_CLIENT_SECRET, 14 | connection: process.env.BROKENECK_AUTH0_CONNECTION 15 | } 16 | }), 17 | ...(provider === 'azure' && { 18 | azure: { 19 | tenantId: process.env.BROKENECK_AZURE_TENANT_ID, 20 | clientId: process.env.BROKENECK_AZURE_CLIENT_ID, 21 | secret: process.env.BROKENECK_AZURE_SECRET 22 | } 23 | }), 24 | ...(provider === 'cognito' && { 25 | cognito: { 26 | region: process.env.BROKENECK_COGNITO_REGION, 27 | userPoolId: process.env.BROKENECK_COGNITO_USER_POOL_ID 28 | } 29 | }), 30 | mercurius: { 31 | graphiql: process.env.BROKENECK_MERCURIUS_GRAPHIQL 32 | } 33 | } 34 | } 35 | 36 | module.exports = envConfig 37 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const envConfig = require('./envConfig') 4 | const pluginConfig = require('./pluginConfig') 5 | 6 | async function plugin(fastify, pluginOptions) { 7 | const options = pluginConfig({ ...envConfig(), ...pluginOptions }) 8 | 9 | if (options.ui) { 10 | fastify.register(require('./plugins/ui'), options) 11 | } 12 | 13 | await fastify.register(require('./plugins/graphql'), options) 14 | await fastify.register(require('./plugins/auth'), options) 15 | } 16 | 17 | module.exports = plugin 18 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugin.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Fastify = require('fastify') 4 | const tap = require('tap') 5 | const proxyquire = require('proxyquire') 6 | 7 | const plugin = proxyquire('./plugin', { 8 | './plugins/ui': async () => {}, 9 | './plugins/auth': async () => {}, 10 | './plugins/graphql': async () => {} 11 | }) 12 | 13 | const test = tap.test 14 | 15 | test('no options', async t => { 16 | const fastify = Fastify() 17 | 18 | t.rejects(fastify.register(plugin)) 19 | }) 20 | 21 | test('config from env', async t => { 22 | let processEnv 23 | 24 | t.beforeEach(async () => { 25 | processEnv = { ...process.env } 26 | }) 27 | 28 | t.afterEach(async () => { 29 | process.env = processEnv 30 | }) 31 | 32 | t.test('provider', async t => { 33 | t.test('cognito', async t => { 34 | process.env.BROKENECK_PROVIDER = 'cognito' 35 | process.env.BROKENECK_COGNITO_REGION = 'region' 36 | process.env.BROKENECK_COGNITO_USER_POOL_ID = 'user pool id' 37 | 38 | t.resolves(Fastify().register(plugin)) 39 | }) 40 | 41 | t.test('auth0', async t => { 42 | process.env.BROKENECK_PROVIDER = 'auth0' 43 | process.env.BROKENECK_AUTH0_DOMAIN = 'domain' 44 | process.env.BROKENECK_AUTH0_CLIENT_ID = 'client id' 45 | process.env.BROKENECK_AUTH0_CLIENT_SECRET = 'client secret' 46 | process.env.BROKENECK_AUTH0_CONNECTION = 'connection' 47 | 48 | t.resolves(Fastify().register(plugin)) 49 | }) 50 | 51 | t.test('azure', async t => { 52 | process.env.BROKENECK_PROVIDER = 'azure' 53 | process.env.BROKENECK_AZURE_TENANT_ID = 'tenant id' 54 | process.env.BROKENECK_AZURE_CLIENT_ID = 'client id' 55 | process.env.BROKENECK_AZURE_SECRET = 'secret' 56 | 57 | t.resolves(Fastify().register(plugin)) 58 | }) 59 | }) 60 | 61 | t.test('ui', async t => { 62 | // to satisfy other required options 63 | process.env.BROKENECK_PROVIDER = 'cognito' 64 | process.env.BROKENECK_COGNITO_REGION = 'region' 65 | process.env.BROKENECK_COGNITO_USER_POOL_ID = 'user pool id' 66 | 67 | process.env.BROKENECK_UI = true 68 | 69 | t.resolves(Fastify().register(plugin)) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/pluginConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const envSchema = require('env-schema') 4 | const S = require('fluent-json-schema') 5 | const Ajv = require('ajv') 6 | 7 | const uiSchema = S.object() 8 | .prop('basename', S.string().default('')) 9 | .prop('serverUrl', S.string().default('/graphql')) 10 | 11 | const pluginSchema = S.object() 12 | .prop('ui', S.oneOf([uiSchema, S.boolean()]).default(false)) 13 | .prop('provider', S.string().enum(['auth0', 'cognito', 'azure']).required()) 14 | .prop( 15 | 'auth0', 16 | S.object() 17 | .prop('domain', S.string().required()) 18 | .prop('clientId', S.string().required()) 19 | .prop('clientSecret', S.string().required()) 20 | .prop('connection', S.string().required()) 21 | ) 22 | .prop( 23 | 'cognito', 24 | S.object() 25 | .prop('region', S.string().required()) 26 | .prop('userPoolId', S.string().required()) 27 | ) 28 | .prop( 29 | 'azure', 30 | S.object() 31 | .prop('tenantId', S.string().required()) 32 | .prop('clientId', S.string().required()) 33 | .prop('secret', S.string().required()) 34 | ) 35 | .ifThen(S.object().prop('provider', S.const('auth0')), S.required(['auth0'])) 36 | .ifThen( 37 | S.object().prop('provider', S.const('cognito')), 38 | S.required(['cognito']) 39 | ) 40 | .ifThen(S.object().prop('provider', S.const('azure')), S.required(['azure'])) 41 | .prop('mercurius', S.object()) 42 | 43 | function pluginConfig(data, schema) { 44 | return envSchema({ 45 | data: typeof data === 'object' ? data : {}, 46 | schema: schema || pluginSchema, 47 | ajv: new Ajv({ 48 | allErrors: true, 49 | removeAdditional: true, 50 | useDefaults: true, 51 | coerceTypes: true, 52 | allowUnionTypes: true, 53 | strictSchema: false 54 | }) 55 | }) 56 | } 57 | 58 | module.exports = pluginConfig 59 | module.exports.uiSchema = uiSchema 60 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/auth0/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | const Auth0Provider = require('./provider') 6 | const typeDefs = require('./typeDefs') 7 | 8 | async function auth0(fastify, options) { 9 | const logger = fastify.log.child({ module: 'auth0' }) 10 | 11 | fastify.graphql.extendSchema(typeDefs) 12 | 13 | fastify.decorate('provider', new Auth0Provider(options, logger)) 14 | } 15 | 16 | module.exports = fp(auth0) 17 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/auth0/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tap = require('tap') 4 | const proxyquire = require('proxyquire') 5 | const sinon = require('sinon') 6 | const Fastify = require('fastify') 7 | 8 | tap.test('auth0', async t => { 9 | const provider = sinon.stub() 10 | 11 | const auth0 = proxyquire('./', { 12 | './provider': function () { 13 | return provider 14 | } 15 | }) 16 | 17 | t.afterEach(async () => { 18 | sinon.restore() 19 | }) 20 | 21 | t.test('registers the plugin', async t => { 22 | const fastify = Fastify() 23 | fastify.graphql = { 24 | extendSchema: sinon.stub() 25 | } 26 | 27 | await t.resolves(fastify.register(auth0)) 28 | 29 | sinon.assert.calledOnce(fastify.graphql.extendSchema) 30 | t.equal(fastify.provider, provider) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/auth0/provider.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ManagementClient = require('auth0').ManagementClient 4 | 5 | function getNextPage(data, property, page) { 6 | return data[property].length + data.start < data.total 7 | ? (page + 2).toString() 8 | : undefined 9 | } 10 | 11 | function Auth0Provider(options, logger) { 12 | const auth0 = new ManagementClient({ 13 | domain: options.domain, 14 | clientId: options.clientId, 15 | clientSecret: options.clientSecret 16 | }) 17 | 18 | return { 19 | meta: { 20 | name: 'auth0', 21 | capabilities: { 22 | canSearchGroups: true 23 | } 24 | }, 25 | async listUsers({ pageNumber, pageSize, search }) { 26 | const page = pageNumber ? Number(pageNumber) - 1 : 0 27 | 28 | const data = await auth0.getUsers({ 29 | page: page, 30 | per_page: pageSize, 31 | include_totals: true, 32 | q: search ? `name:*${search}*` : undefined 33 | }) 34 | 35 | const users = { 36 | data: data.users, 37 | nextPage: getNextPage(data, 'users', page) 38 | } 39 | 40 | logger.debug({ users }, 'loaded users') 41 | 42 | return users 43 | }, 44 | async getUser(id) { 45 | const user = await auth0.getUser({ id }) 46 | 47 | logger.debug({ user }) 48 | 49 | return user 50 | }, 51 | async listGroups({ pageNumber, pageSize, search }) { 52 | const page = pageNumber ? Number(pageNumber) - 1 : 0 53 | 54 | const data = await auth0.getRoles({ 55 | page: page, 56 | per_page: pageSize, 57 | include_totals: true, 58 | name_filter: search || undefined 59 | }) 60 | 61 | const groups = { 62 | data: data.roles, 63 | nextPage: getNextPage(data, 'roles', page) 64 | } 65 | 66 | logger.debug({ groups }, 'loaded groups') 67 | 68 | return groups 69 | }, 70 | async getGroup(id) { 71 | const group = await auth0.getRole({ id }) 72 | 73 | logger.debug({ group }) 74 | 75 | return group 76 | }, 77 | async createUser(input) { 78 | const user = await auth0.createUser({ 79 | connection: options.connection, 80 | ...input 81 | }) 82 | 83 | logger.debug({ user }) 84 | 85 | return user 86 | }, 87 | async editUser(id, input) { 88 | await auth0.updateUser({ id }, input) 89 | return true 90 | }, 91 | async createGroup(input) { 92 | const group = await auth0.createRole(input) 93 | 94 | logger.debug({ group }) 95 | 96 | return group 97 | }, 98 | async editGroup(id, input) { 99 | await auth0.updateRole({ id }, input) 100 | return true 101 | }, 102 | async listGroupsForUser(user) { 103 | const groups = await auth0.getUserRoles({ id: user.user_id }) 104 | 105 | logger.debug({ groups }) 106 | 107 | return groups 108 | }, 109 | async listUsersForGroup({ group, pageSize, pageNumber }) { 110 | const page = pageNumber ? Number(pageNumber) - 1 : 0 111 | 112 | const data = await auth0.getUsersInRole({ 113 | id: group.id, 114 | page, 115 | per_page: pageSize, 116 | include_totals: true 117 | }) 118 | 119 | const users = { 120 | data: data.users, 121 | nextPage: getNextPage(data, 'users', page) 122 | } 123 | 124 | logger.debug({ users }) 125 | 126 | return users 127 | }, 128 | addUserToGroup({ userId, groupId }) { 129 | return auth0.assignRolestoUser({ id: userId }, { roles: [groupId] }) 130 | }, 131 | removeUserFromGroup({ userId, groupId }) { 132 | return auth0.removeRolesFromUser({ id: userId }, { roles: [groupId] }) 133 | }, 134 | deleteUser({ id }) { 135 | return auth0.deleteUser({ id }) 136 | }, 137 | deleteGroup({ id }) { 138 | return auth0.deleteRole({ id }) 139 | } 140 | } 141 | } 142 | 143 | module.exports = Auth0Provider 144 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/auth0/typeDefs.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { default: gql } = require('graphql-tag') 4 | 5 | const typeDefs = gql` 6 | extend type User { 7 | user_id: ID! 8 | email: String! 9 | } 10 | 11 | extend type Group { 12 | id: ID! 13 | name: String! 14 | description: String 15 | } 16 | 17 | extend input UserInput { 18 | email: String! 19 | password: String! 20 | } 21 | 22 | extend input EditUserInput { 23 | email: String! 24 | } 25 | 26 | extend input GroupInput { 27 | name: String! 28 | description: String 29 | } 30 | 31 | extend input EditGroupInput { 32 | name: String! 33 | description: String 34 | } 35 | ` 36 | 37 | module.exports = typeDefs 38 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/azure/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | const msRestNodeAuth = require('@azure/ms-rest-nodeauth') 5 | 6 | const AzureProvider = require('./provider') 7 | const typeDefs = require('./typeDefs') 8 | 9 | async function azure(fastify, options) { 10 | const logger = fastify.log.child({ module: 'azure' }) 11 | 12 | fastify.graphql.extendSchema(typeDefs) 13 | 14 | const authResponse = 15 | await msRestNodeAuth.loginWithServicePrincipalSecretWithAuthResponse( 16 | options.clientId, 17 | options.secret, 18 | options.tenantId, 19 | { 20 | tokenAudience: 'graph' 21 | } 22 | ) 23 | 24 | fastify.decorate( 25 | 'provider', 26 | new AzureProvider(options, authResponse.credentials, logger) 27 | ) 28 | } 29 | 30 | module.exports = fp(azure) 31 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/azure/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tap = require('tap') 4 | const proxyquire = require('proxyquire') 5 | const sinon = require('sinon') 6 | const Fastify = require('fastify') 7 | 8 | tap.test('azure', async t => { 9 | const provider = sinon.stub() 10 | 11 | const auth0 = proxyquire('./', { 12 | './provider': function () { 13 | return provider 14 | }, 15 | '@azure/ms-rest-nodeauth': { 16 | loginWithServicePrincipalSecretWithAuthResponse: sinon.stub().resolves({}) 17 | } 18 | }) 19 | 20 | t.afterEach(async () => { 21 | sinon.restore() 22 | }) 23 | 24 | t.test('registers the plugin', async t => { 25 | const fastify = Fastify() 26 | fastify.graphql = { 27 | extendSchema: sinon.stub() 28 | } 29 | 30 | await t.resolves(fastify.register(auth0)) 31 | 32 | sinon.assert.calledOnce(fastify.graphql.extendSchema) 33 | t.equal(fastify.provider, provider) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/azure/operations.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Serializer } = require('@azure/ms-rest-js') 4 | const { GraphRbacManagementMappers: Mappers } = require('@azure/graph') 5 | 6 | const Parameters = require('./parameters') 7 | 8 | const serializer = new Serializer(Mappers) 9 | 10 | const listUsersOperationSpec = { 11 | httpMethod: 'GET', 12 | path: '{tenantID}/users', 13 | urlParameters: [Parameters.tenantID], 14 | queryParameters: [ 15 | Parameters.filter, 16 | Parameters.top, 17 | Parameters.apiVersion, 18 | Parameters.search 19 | ], 20 | headerParameters: [Parameters.acceptLanguage], 21 | responses: { 22 | 200: { 23 | bodyMapper: Mappers.UserListResult 24 | }, 25 | default: { 26 | bodyMapper: Mappers.GraphError 27 | } 28 | }, 29 | serializer 30 | } 31 | 32 | const listUsersNextOperationSpec = { 33 | httpMethod: 'GET', 34 | path: '{tenantID}/{nextLink}', 35 | urlParameters: [Parameters.nextLink, Parameters.tenantID], 36 | queryParameters: [Parameters.apiVersion, Parameters.top, Parameters.search], 37 | headerParameters: [Parameters.acceptLanguage], 38 | responses: { 39 | 200: { 40 | bodyMapper: Mappers.UserListResult 41 | }, 42 | default: { 43 | bodyMapper: Mappers.GraphError 44 | } 45 | }, 46 | serializer 47 | } 48 | 49 | const listGroupsOperationSpec = { 50 | httpMethod: 'GET', 51 | path: '{tenantID}/groups', 52 | urlParameters: [Parameters.tenantID], 53 | queryParameters: [ 54 | Parameters.filter, 55 | Parameters.top, 56 | Parameters.apiVersion, 57 | Parameters.search 58 | ], 59 | headerParameters: [Parameters.acceptLanguage], 60 | responses: { 61 | 200: { 62 | bodyMapper: Mappers.GroupListResult 63 | }, 64 | default: { 65 | bodyMapper: Mappers.GraphError 66 | } 67 | }, 68 | serializer 69 | } 70 | 71 | const listGroupsNextOperationSpec = { 72 | httpMethod: 'GET', 73 | path: '{tenantID}/{nextLink}', 74 | urlParameters: [Parameters.nextLink, Parameters.tenantID], 75 | queryParameters: [Parameters.apiVersion, Parameters.top], 76 | headerParameters: [Parameters.acceptLanguage], 77 | responses: { 78 | 200: { 79 | bodyMapper: Mappers.GroupListResult 80 | }, 81 | default: { 82 | bodyMapper: Mappers.GraphError 83 | } 84 | }, 85 | serializer 86 | } 87 | 88 | const listGroupsUsersOperationSpec = { 89 | httpMethod: 'GET', 90 | path: '{tenantID}/groups/{groupId}/members', 91 | urlParameters: [Parameters.tenantID, Parameters.groupId], 92 | queryParameters: [ 93 | Parameters.filter, 94 | Parameters.top, 95 | Parameters.apiVersion, 96 | Parameters.search 97 | ], 98 | headerParameters: [Parameters.acceptLanguage], 99 | responses: { 100 | 200: { 101 | bodyMapper: Mappers.UserListResult 102 | }, 103 | default: { 104 | bodyMapper: Mappers.GraphError 105 | } 106 | }, 107 | serializer 108 | } 109 | 110 | const listGroupsUsersNextOperationSpec = { 111 | httpMethod: 'GET', 112 | path: '{tenantID}/{nextLink}', 113 | urlParameters: [Parameters.nextLink, Parameters.tenantID], 114 | queryParameters: [Parameters.apiVersion, Parameters.top], 115 | headerParameters: [Parameters.acceptLanguage], 116 | responses: { 117 | 200: { 118 | bodyMapper: Mappers.UserListResult 119 | }, 120 | default: { 121 | bodyMapper: Mappers.GraphError 122 | } 123 | }, 124 | serializer 125 | } 126 | 127 | module.exports = { 128 | listUsersOperationSpec, 129 | listUsersNextOperationSpec, 130 | listGroupsOperationSpec, 131 | listGroupsNextOperationSpec, 132 | listGroupsUsersOperationSpec, 133 | listGroupsUsersNextOperationSpec 134 | } 135 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/azure/parameters.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const acceptLanguage = { 4 | parameterPath: 'acceptLanguage', 5 | mapper: { 6 | serializedName: 'accept-language', 7 | defaultValue: 'en-US', 8 | type: { 9 | name: 'String' 10 | } 11 | } 12 | } 13 | const apiVersion = { 14 | parameterPath: 'apiVersion', 15 | mapper: { 16 | required: true, 17 | serializedName: 'api-version', 18 | type: { 19 | name: 'String' 20 | } 21 | } 22 | } 23 | const filter = { 24 | parameterPath: ['options', 'filter'], 25 | mapper: { 26 | serializedName: '$filter', 27 | type: { 28 | name: 'String' 29 | } 30 | } 31 | } 32 | 33 | const top = { 34 | parameterPath: ['options', 'top'], 35 | mapper: { 36 | serializedName: '$top', 37 | type: { 38 | name: 'Number' 39 | } 40 | } 41 | } 42 | 43 | const search = { 44 | parameterPath: ['options', 'search'], 45 | mapper: { 46 | serializedName: '$search', 47 | type: { 48 | name: 'String' 49 | } 50 | } 51 | } 52 | 53 | const nextLink = { 54 | parameterPath: 'nextLink', 55 | mapper: { 56 | required: true, 57 | serializedName: 'nextLink', 58 | type: { 59 | name: 'String' 60 | } 61 | }, 62 | skipEncoding: true 63 | } 64 | const tenantID = { 65 | parameterPath: 'tenantID', 66 | mapper: { 67 | required: true, 68 | serializedName: 'tenantID', 69 | type: { 70 | name: 'String' 71 | } 72 | } 73 | } 74 | const groupId = { 75 | parameterPath: 'groupId', 76 | mapper: { 77 | required: true, 78 | serializedName: 'groupId', 79 | type: { 80 | name: 'String' 81 | } 82 | } 83 | } 84 | 85 | module.exports = { 86 | tenantID, 87 | acceptLanguage, 88 | filter, 89 | apiVersion, 90 | top, 91 | search, 92 | nextLink, 93 | groupId 94 | } 95 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/azure/provider.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { GraphRbacManagementClient } = require('@azure/graph') 4 | 5 | const { 6 | listUsersNextOperationSpec, 7 | listUsersOperationSpec, 8 | listGroupsOperationSpec, 9 | listGroupsNextOperationSpec, 10 | listGroupsUsersOperationSpec, 11 | listGroupsUsersNextOperationSpec 12 | } = require('./operations') 13 | 14 | function AzureProvider(options, credentials, logger) { 15 | const azure = new GraphRbacManagementClient(credentials, options.tenantId) 16 | 17 | return { 18 | meta: { 19 | name: 'azure', 20 | capabilities: { 21 | canSearchGroups: false 22 | } 23 | }, 24 | async listUsers({ pageNumber, pageSize, search }) { 25 | const options = { top: pageSize, search: `"displayName:${search}"` } 26 | 27 | const result = await (pageNumber 28 | ? azure.sendOperationRequest( 29 | { 30 | nextLink: pageNumber, 31 | options 32 | }, 33 | listUsersNextOperationSpec 34 | ) 35 | : azure.sendOperationRequest({ options }, listUsersOperationSpec)) 36 | 37 | const users = { data: result, nextPage: result.odatanextLink } 38 | 39 | logger.debug({ users }, 'loaded users') 40 | 41 | return users 42 | }, 43 | async getUser(id) { 44 | const user = await azure.users.get(id) 45 | 46 | logger.debug({ user }, 'loaded user') 47 | 48 | return user 49 | }, 50 | async listGroups({ pageNumber, pageSize }) { 51 | const options = { top: pageSize } 52 | 53 | const result = await (pageNumber 54 | ? azure.sendOperationRequest( 55 | { 56 | nextLink: pageNumber, 57 | options 58 | }, 59 | listGroupsNextOperationSpec 60 | ) 61 | : azure.sendOperationRequest({ options }, listGroupsOperationSpec)) 62 | 63 | const groups = { data: result, nextPage: result.odatanextLink } 64 | 65 | logger.debug({ groups }, 'loaded groups') 66 | 67 | return groups 68 | }, 69 | async getGroup(id) { 70 | const group = await azure.groups.get(id) 71 | 72 | logger.debug({ group }, 'loaded group') 73 | 74 | return group 75 | }, 76 | createUser({ password, forceChangePasswordNextLogin, ...input }) { 77 | return azure.users.create({ 78 | ...input, 79 | accountEnabled: !!input.accountEnabled, 80 | passwordProfile: { 81 | password, 82 | forceChangePasswordNextLogin: !!forceChangePasswordNextLogin 83 | } 84 | }) 85 | }, 86 | async editUser(id, input) { 87 | await azure.users.update(id, input) 88 | return true 89 | }, 90 | createGroup(input) { 91 | return azure.groups.create(input) 92 | }, 93 | async editGroup(id, input) { 94 | await azure.groups.update(id, input) 95 | return true 96 | }, 97 | async listGroupsForUser(user) { 98 | const userGroupsIds = await azure.users.getMemberGroups(user.objectId, { 99 | securityEnabledOnly: false 100 | }) 101 | 102 | logger.debug({ userGroupsIds }, 'loaded groups for user') 103 | 104 | return Promise.all( 105 | userGroupsIds.map(groupId => azure.groups.get(groupId)) 106 | ) 107 | }, 108 | async listUsersForGroup({ group, pageSize, pageNumber }) { 109 | const options = { top: pageSize } 110 | 111 | const result = await (pageNumber 112 | ? azure.sendOperationRequest( 113 | { 114 | nextLink: pageNumber, 115 | options 116 | }, 117 | listGroupsUsersNextOperationSpec 118 | ) 119 | : azure.sendOperationRequest( 120 | { groupId: group.objectId, options }, 121 | listGroupsUsersOperationSpec 122 | )) 123 | 124 | const users = { data: result, nextPage: result.odatanextLink } 125 | 126 | logger.debug({ users }, "loaded group's users") 127 | 128 | return users 129 | }, 130 | addUserToGroup({ userId, groupId }) { 131 | const userUrl = `https://graph.windows.net/${options.tenantId}/directoryObjects/${userId}` 132 | 133 | return azure.groups.addMember(groupId, { 134 | url: userUrl 135 | }) 136 | }, 137 | removeUserFromGroup({ userId, groupId }) { 138 | return azure.groups.removeMember(groupId, userId) 139 | }, 140 | deleteUser({ id }) { 141 | return azure.users.deleteMethod(id) 142 | }, 143 | deleteGroup({ id }) { 144 | return azure.groups.deleteMethod(id) 145 | } 146 | } 147 | } 148 | 149 | module.exports = AzureProvider 150 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/azure/typeDefs.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { default: gql } = require('graphql-tag') 4 | 5 | const typeDefs = gql` 6 | extend type User { 7 | objectId: ID! 8 | displayName: String! 9 | mailNickname: String! 10 | createdDateTime: DateTime 11 | accountEnabled: Boolean 12 | } 13 | 14 | extend type Group { 15 | objectId: ID! 16 | displayName: String! 17 | mailNickname: String! 18 | } 19 | 20 | extend input UserInput { 21 | displayName: String! 22 | userPrincipalName: String! 23 | mailNickname: String! 24 | password: String! 25 | accountEnabled: Boolean! 26 | forceChangePasswordNextLogin: Boolean! 27 | } 28 | 29 | extend input EditUserInput { 30 | displayName: String! 31 | accountEnabled: Boolean! 32 | } 33 | 34 | extend input GroupInput { 35 | displayName: String! 36 | mailNickname: String! 37 | } 38 | 39 | extend input EditGroupInput { 40 | displayName: String! 41 | mailNickname: String! 42 | } 43 | ` 44 | 45 | module.exports = typeDefs 46 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/cognito/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | const CognitoProvider = require('./provider') 6 | const typeDefs = require('./typeDefs') 7 | 8 | async function cognito(fastify, options) { 9 | const logger = fastify.log.child({ module: 'cognito' }) 10 | 11 | fastify.graphql.extendSchema(typeDefs) 12 | 13 | fastify.decorate('provider', new CognitoProvider(options, logger)) 14 | } 15 | 16 | module.exports = fp(cognito) 17 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/cognito/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tap = require('tap') 4 | const proxyquire = require('proxyquire') 5 | const sinon = require('sinon') 6 | const Fastify = require('fastify') 7 | 8 | tap.test('cognito', async t => { 9 | const provider = sinon.stub() 10 | 11 | const cognito = proxyquire('./', { 12 | './provider': function () { 13 | return provider 14 | } 15 | }) 16 | 17 | t.afterEach(async () => { 18 | sinon.restore() 19 | }) 20 | 21 | t.test('registers the plugin', async t => { 22 | const fastify = Fastify() 23 | fastify.graphql = { 24 | extendSchema: sinon.stub() 25 | } 26 | 27 | await t.resolves(fastify.register(cognito)) 28 | 29 | sinon.assert.calledOnce(fastify.graphql.extendSchema) 30 | t.equal(fastify.provider, provider) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/cognito/provider.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const AWS = require('aws-sdk') 4 | 5 | function CognitoProvider(options, logger) { 6 | const cognito = new AWS.CognitoIdentityServiceProvider({ 7 | region: options.region 8 | }) 9 | 10 | const UserPoolId = options.userPoolId 11 | 12 | return { 13 | meta: { 14 | name: 'cognito', 15 | capabilities: { 16 | canSearchGroups: false 17 | } 18 | }, 19 | async listUsers({ pageNumber, pageSize, search }) { 20 | const result = await cognito 21 | .listUsers({ 22 | UserPoolId, 23 | Limit: pageSize, 24 | PaginationToken: pageNumber || undefined, 25 | Filter: search ? `username ^= "${search}"` : undefined 26 | }) 27 | .promise() 28 | 29 | const users = { data: result.Users, nextPage: result.PaginationToken } 30 | 31 | logger.debug({ users }) 32 | 33 | return users 34 | }, 35 | async getUser(id) { 36 | const user = await cognito 37 | .adminGetUser({ 38 | UserPoolId, 39 | Username: id 40 | }) 41 | .promise() 42 | 43 | logger.debug({ user }) 44 | 45 | return user 46 | }, 47 | async listGroups({ pageSize, pageNumber }) { 48 | const result = await cognito 49 | .listGroups({ 50 | UserPoolId, 51 | Limit: pageSize, 52 | NextToken: pageNumber || undefined 53 | }) 54 | .promise() 55 | 56 | const groups = { data: result.Groups, nextPage: result.NextToken } 57 | 58 | logger.debug({ groups }) 59 | 60 | return groups 61 | }, 62 | async getGroup(id) { 63 | const group = await cognito 64 | .getGroup({ 65 | UserPoolId, 66 | GroupName: id 67 | }) 68 | .promise() 69 | 70 | logger.debug({ group }) 71 | 72 | return group.Group 73 | }, 74 | async createUser(input) { 75 | const result = await cognito 76 | .adminCreateUser({ 77 | UserPoolId, 78 | ...input 79 | }) 80 | .promise() 81 | 82 | return result.User 83 | }, 84 | async editUser(id, input) { 85 | const { Enabled } = input 86 | 87 | if (Enabled) { 88 | await cognito 89 | .adminEnableUser({ 90 | UserPoolId, 91 | Username: id 92 | }) 93 | .promise() 94 | } else { 95 | await cognito 96 | .adminDisableUser({ 97 | UserPoolId, 98 | Username: id 99 | }) 100 | .promise() 101 | } 102 | 103 | return true 104 | }, 105 | async createGroup(input) { 106 | const result = await cognito 107 | .createGroup({ 108 | UserPoolId, 109 | ...input 110 | }) 111 | .promise() 112 | 113 | return result.Group 114 | }, 115 | async editGroup(id, input) { 116 | await cognito 117 | .updateGroup({ 118 | UserPoolId, 119 | GroupName: id, 120 | ...input 121 | }) 122 | .promise() 123 | return true 124 | }, 125 | async listGroupsForUser(user) { 126 | const userGroups = await cognito 127 | .adminListGroupsForUser({ 128 | UserPoolId, 129 | Username: user.Username 130 | }) 131 | .promise() 132 | 133 | logger.debug({ userGroups }) 134 | 135 | return userGroups.Groups 136 | }, 137 | async listUsersForGroup({ group, pageSize, pageNumber }) { 138 | const result = await cognito 139 | .listUsersInGroup({ 140 | UserPoolId, 141 | GroupName: group.GroupName, 142 | Limit: pageSize, 143 | NextToken: pageNumber || undefined 144 | }) 145 | .promise() 146 | 147 | const groupUsers = { data: result.Users, nextPage: result.NextToken } 148 | 149 | logger.debug({ groupUsers }) 150 | 151 | return groupUsers 152 | }, 153 | addUserToGroup({ userId, groupId }) { 154 | return cognito 155 | .adminAddUserToGroup({ 156 | UserPoolId, 157 | Username: userId, 158 | GroupName: groupId 159 | }) 160 | .promise() 161 | }, 162 | removeUserFromGroup({ userId, groupId }) { 163 | return cognito 164 | .adminRemoveUserFromGroup({ 165 | UserPoolId, 166 | Username: userId, 167 | GroupName: groupId 168 | }) 169 | .promise() 170 | }, 171 | deleteUser({ id }) { 172 | return cognito 173 | .adminDeleteUser({ 174 | UserPoolId, 175 | Username: id 176 | }) 177 | .promise() 178 | }, 179 | deleteGroup({ id }) { 180 | return cognito 181 | .deleteGroup({ 182 | UserPoolId, 183 | GroupName: id 184 | }) 185 | .promise() 186 | } 187 | } 188 | } 189 | 190 | module.exports = CognitoProvider 191 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/cognito/typeDefs.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { default: gql } = require('graphql-tag') 4 | 5 | const typeDefs = gql` 6 | extend type User { 7 | Username: ID! 8 | Enabled: Boolean 9 | UserStatus: String 10 | UserCreateDate: DateTime 11 | UserLastModifiedDate: DateTime 12 | } 13 | 14 | extend type Group { 15 | GroupName: ID! 16 | Description: String 17 | CreationDate: DateTime 18 | LastModifiedDate: DateTime 19 | } 20 | 21 | extend input UserInput { 22 | Username: String! 23 | } 24 | 25 | extend input EditUserInput { 26 | Enabled: Boolean 27 | } 28 | 29 | extend input GroupInput { 30 | GroupName: String! 31 | Description: String 32 | } 33 | 34 | extend input EditGroupInput { 35 | Description: String 36 | } 37 | ` 38 | 39 | module.exports = typeDefs 40 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | 5 | const authProviders = { 6 | auth0: require('./auth0'), 7 | cognito: require('./cognito'), 8 | azure: require('./azure') 9 | } 10 | 11 | function auth(server, options) { 12 | return server.register( 13 | authProviders[options.provider], 14 | options[options.provider] 15 | ) 16 | } 17 | 18 | module.exports = fp(auth) 19 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/auth/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tap = require('tap') 4 | const proxyquire = require('proxyquire') 5 | const sinon = require('sinon') 6 | const Fastify = require('fastify') 7 | 8 | tap.test('auth', async t => { 9 | const auth0 = sinon.stub().resolves() 10 | const azure = sinon.stub().resolves() 11 | const cognito = sinon.stub().resolves() 12 | 13 | const auth = proxyquire('./', { 14 | './auth0': auth0, 15 | './azure': azure, 16 | './cognito': cognito 17 | }) 18 | 19 | t.afterEach(async () => { 20 | sinon.restore() 21 | }) 22 | 23 | t.test('auth0', async t => { 24 | await t.resolves( 25 | Fastify().register(auth, { provider: 'auth0', auth0: { some: 'config' } }) 26 | ) 27 | 28 | sinon.assert.calledOnceWithMatch(auth0, sinon.match.any, { some: 'config' }) 29 | }) 30 | 31 | t.test('azure', async t => { 32 | await t.resolves( 33 | Fastify().register(auth, { provider: 'azure', azure: { some: 'config' } }) 34 | ) 35 | 36 | sinon.assert.calledOnceWithMatch(azure, sinon.match.any, { some: 'config' }) 37 | }) 38 | 39 | t.test('cognito', async t => { 40 | await t.resolves( 41 | Fastify().register(auth, { 42 | provider: 'cognito', 43 | cognito: { some: 'config' } 44 | }) 45 | ) 46 | 47 | sinon.assert.calledOnceWithMatch(cognito, sinon.match.any, { 48 | some: 'config' 49 | }) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/graphql/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | const mercurius = require('mercurius') 5 | const { makeExecutableSchema } = require('@graphql-tools/schema') 6 | 7 | const typeDefs = require('./typeDefs') 8 | const makeResolvers = require('./resolvers') 9 | const makeLoaders = require('./loaders') 10 | 11 | async function graphql(fastify, options) { 12 | const resolvers = makeResolvers(fastify) 13 | const loaders = makeLoaders(fastify) 14 | 15 | const schema = makeExecutableSchema({ 16 | typeDefs, 17 | resolvers 18 | }) 19 | 20 | fastify.register(mercurius, { 21 | schema, 22 | loaders, 23 | ...options.mercurius 24 | }) 25 | } 26 | 27 | module.exports = fp(graphql) 28 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/graphql/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tap = require('tap') 4 | const proxyquire = require('proxyquire') 5 | const sinon = require('sinon') 6 | const Fastify = require('fastify') 7 | 8 | tap.test('graphql', async t => { 9 | const mercurius = sinon.stub().resolves() 10 | 11 | const graphql = proxyquire('./', { 12 | mercurius 13 | }) 14 | 15 | t.afterEach(async () => { 16 | sinon.restore() 17 | }) 18 | 19 | t.test('registers the plugin', async t => { 20 | const fastify = Fastify() 21 | 22 | await t.resolves(fastify.register(graphql)) 23 | 24 | sinon.assert.called(mercurius) 25 | }) 26 | }) 27 | 28 | tap.test('graphql with mercurius', async t => { 29 | const graphql = require('./') 30 | 31 | t.test('registers the plugin', async t => { 32 | const fastify = Fastify() 33 | 34 | fastify.decorate('provider', { 35 | meta: { 36 | name: 'test' 37 | } 38 | }) 39 | fastify.register(graphql) 40 | 41 | const query = ` 42 | query { 43 | provider { 44 | name 45 | } 46 | } 47 | ` 48 | 49 | const response = await fastify.inject({ 50 | method: 'POST', 51 | headers: { 'content-type': 'application/json' }, 52 | url: '/graphql', 53 | body: JSON.stringify({ query }) 54 | }) 55 | 56 | t.same(response.json(), { 57 | data: { 58 | provider: { 59 | name: 'test' 60 | } 61 | } 62 | }) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/graphql/loaders.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function makeLoaders(fastify) { 4 | return { 5 | User: { 6 | groups(queries) { 7 | return Promise.all( 8 | queries.map(({ obj }) => fastify.provider.listGroupsForUser(obj)) 9 | ) 10 | } 11 | }, 12 | Group: { 13 | users(queries, { reply }) { 14 | return Promise.all( 15 | queries.map(({ obj }) => 16 | fastify.provider.listUsersForGroup({ 17 | group: obj, 18 | pageSize: reply.request.body.variables.pageSize, 19 | pageNumber: reply.request.body.variables.pageNumber 20 | }) 21 | ) 22 | ) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/graphql/loaders.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tap = require('tap') 4 | const sinon = require('sinon') 5 | const faker = require('faker') 6 | 7 | const makeLoaders = require('./loaders') 8 | 9 | tap.test('loaders', async t => { 10 | const fastify = { 11 | provider: {} 12 | } 13 | 14 | const loaders = makeLoaders(fastify) 15 | 16 | t.afterEach(async () => { 17 | sinon.restore() 18 | }) 19 | 20 | t.test('User.groups', async t => { 21 | const groups = faker.random.arrayElements() 22 | const queries = groups.map(g => ({ obj: g })) 23 | 24 | fastify.provider.listGroupsForUser = sinon.stub() 25 | 26 | queries.forEach((q, i) => { 27 | fastify.provider.listGroupsForUser.withArgs(q.obj).resolves(groups[i]) 28 | }) 29 | 30 | const result = await loaders.User.groups(queries) 31 | 32 | t.same(result, groups) 33 | }) 34 | 35 | t.test('Group.users', async t => { 36 | const users = faker.random.arrayElements() 37 | const queries = users.map(g => ({ obj: g })) 38 | 39 | fastify.provider.listUsersForGroup = sinon.stub() 40 | 41 | queries.forEach((q, i) => { 42 | fastify.provider.listUsersForGroup 43 | .withArgs({ group: q.obj, pageSize: undefined, pageNumber: undefined }) 44 | .resolves(users[i]) 45 | }) 46 | 47 | const reply = { 48 | request: { 49 | body: { 50 | variables: {} 51 | } 52 | } 53 | } 54 | 55 | const result = await loaders.Group.users(queries, { reply }) 56 | 57 | t.same(result, users) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/graphql/resolvers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { 4 | GraphQLDate, 5 | GraphQLTime, 6 | GraphQLDateTime 7 | } = require('graphql-iso-date') 8 | 9 | module.exports = function makeResolvers(fastify) { 10 | return { 11 | Date: GraphQLDate, 12 | Time: GraphQLTime, 13 | DateTime: GraphQLDateTime, 14 | Query: { 15 | users(_, { pageNumber, pageSize, search }) { 16 | return fastify.provider.listUsers({ pageNumber, pageSize, search }) 17 | }, 18 | user(_, { id }) { 19 | return fastify.provider.getUser(id) 20 | }, 21 | groups(_, { pageNumber, pageSize, search }) { 22 | return fastify.provider.listGroups({ pageNumber, pageSize, search }) 23 | }, 24 | group(_, { id }) { 25 | return fastify.provider.getGroup(id) 26 | }, 27 | provider() { 28 | return fastify.provider.meta 29 | } 30 | }, 31 | Mutation: { 32 | createUser(_, { input }) { 33 | return fastify.provider.createUser(input) 34 | }, 35 | editUser(_, { id, input }) { 36 | return fastify.provider.editUser(id, input) 37 | }, 38 | createGroup(_, { input }) { 39 | return fastify.provider.createGroup(input) 40 | }, 41 | editGroup(_, { id, input }) { 42 | return fastify.provider.editGroup(id, input) 43 | }, 44 | async addUserToGroup(_, { input }) { 45 | await fastify.provider.addUserToGroup(input) 46 | return true 47 | }, 48 | async removeUserFromGroup(_, { input }) { 49 | await fastify.provider.removeUserFromGroup(input) 50 | return true 51 | }, 52 | async deleteUser(_, { input }) { 53 | await fastify.provider.deleteUser(input) 54 | return true 55 | }, 56 | async deleteGroup(_, { input }) { 57 | await fastify.provider.deleteGroup(input) 58 | return true 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/graphql/resolvers.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tap = require('tap') 4 | const sinon = require('sinon') 5 | const faker = require('faker') 6 | 7 | const makeResolvers = require('./resolvers') 8 | 9 | tap.test('resolvers', async t => { 10 | const fastify = { 11 | provider: {} 12 | } 13 | 14 | const resolvers = makeResolvers(fastify) 15 | 16 | t.afterEach(async () => { 17 | sinon.restore() 18 | }) 19 | 20 | t.test('Query', async t => { 21 | t.test('users', async t => { 22 | const users = faker.random.arrayElements() 23 | 24 | fastify.provider.listUsers = sinon.stub().resolves(users) 25 | 26 | const args = { 27 | pageNumber: 1, 28 | pageSize: 2, 29 | search: 'search' 30 | } 31 | const result = await resolvers.Query.users(null, args) 32 | 33 | sinon.assert.calledWith(fastify.provider.listUsers, args) 34 | 35 | t.equal(result, users) 36 | }) 37 | 38 | t.test('user', async t => { 39 | const id = faker.datatype.uuid() 40 | const user = { id } 41 | 42 | fastify.provider.getUser = sinon.stub().resolves(user) 43 | 44 | const result = await resolvers.Query.user(null, { id }) 45 | 46 | t.equal(result, user) 47 | 48 | sinon.assert.calledOnceWithMatch(fastify.provider.getUser, id) 49 | }) 50 | 51 | t.test('groups', async t => { 52 | const groups = faker.random.arrayElements() 53 | 54 | fastify.provider.listGroups = sinon.stub().resolves(groups) 55 | 56 | const args = { 57 | pageNumber: 1, 58 | pageSize: 2, 59 | search: 'search' 60 | } 61 | const result = await resolvers.Query.groups(null, args) 62 | 63 | sinon.assert.calledWith(fastify.provider.listGroups, args) 64 | 65 | t.equal(result, groups) 66 | }) 67 | 68 | t.test('group', async t => { 69 | const id = faker.datatype.uuid() 70 | const group = { id } 71 | 72 | fastify.provider.getGroup = sinon.stub().resolves(group) 73 | 74 | const result = await resolvers.Query.group(null, { id }) 75 | 76 | t.equal(result, group) 77 | 78 | sinon.assert.calledOnceWithMatch(fastify.provider.getGroup, id) 79 | }) 80 | 81 | t.test('provider', async t => { 82 | const name = faker.random.word() 83 | 84 | fastify.provider.meta = { name } 85 | 86 | const result = await resolvers.Query.provider() 87 | 88 | t.same(result, { name }) 89 | }) 90 | }) 91 | 92 | t.test('Mutation', async t => { 93 | t.test('createUser', async t => { 94 | const user = { id: faker.datatype.uuid() } 95 | 96 | fastify.provider.createUser = sinon.stub().resolves(user) 97 | 98 | const result = await resolvers.Mutation.createUser(null, { input: user }) 99 | 100 | t.equal(result, user) 101 | sinon.assert.calledWith(fastify.provider.createUser, user) 102 | }) 103 | 104 | t.test('editUser', async t => { 105 | const id = faker.datatype.uuid() 106 | const input = { property: faker.random.word() } 107 | 108 | fastify.provider.editUser = sinon.stub().resolves() 109 | 110 | await resolvers.Mutation.editUser(null, { id, input }) 111 | 112 | sinon.assert.calledWith(fastify.provider.editUser, id, input) 113 | }) 114 | 115 | t.test('createGroup', async t => { 116 | const group = { id: faker.datatype.uuid() } 117 | 118 | fastify.provider.createGroup = sinon.stub().resolves(group) 119 | 120 | const result = await resolvers.Mutation.createGroup(null, { 121 | input: group 122 | }) 123 | 124 | t.equal(result, group) 125 | sinon.assert.calledWith(fastify.provider.createGroup, group) 126 | }) 127 | 128 | t.test('editGroup', async t => { 129 | const id = faker.datatype.uuid() 130 | const input = { property: faker.random.word() } 131 | 132 | fastify.provider.editGroup = sinon.stub().resolves() 133 | 134 | await resolvers.Mutation.editGroup(null, { id, input }) 135 | 136 | sinon.assert.calledWith(fastify.provider.editGroup, id, input) 137 | }) 138 | 139 | t.test('addUserToGroup', async t => { 140 | const input = { id: faker.datatype.uuid() } 141 | 142 | fastify.provider.addUserToGroup = sinon.stub().resolves(input) 143 | 144 | const result = await resolvers.Mutation.addUserToGroup(null, { 145 | input 146 | }) 147 | 148 | t.equal(result, true) 149 | sinon.assert.calledWith(fastify.provider.addUserToGroup, input) 150 | }) 151 | 152 | t.test('removeUserFromGroup', async t => { 153 | const input = { id: faker.datatype.uuid() } 154 | 155 | fastify.provider.removeUserFromGroup = sinon.stub().resolves(input) 156 | 157 | const result = await resolvers.Mutation.removeUserFromGroup(null, { 158 | input 159 | }) 160 | 161 | t.equal(result, true) 162 | sinon.assert.calledWith(fastify.provider.removeUserFromGroup, input) 163 | }) 164 | 165 | t.test('deleteUser', async t => { 166 | const input = { id: faker.datatype.uuid() } 167 | 168 | fastify.provider.deleteUser = sinon.stub().resolves(input) 169 | 170 | const result = await resolvers.Mutation.deleteUser(null, { 171 | input 172 | }) 173 | 174 | t.equal(result, true) 175 | sinon.assert.calledWith(fastify.provider.deleteUser, input) 176 | }) 177 | 178 | t.test('deleteGroup', async t => { 179 | const input = { id: faker.datatype.uuid() } 180 | 181 | fastify.provider.deleteGroup = sinon.stub().resolves(input) 182 | 183 | const result = await resolvers.Mutation.deleteGroup(null, { 184 | input 185 | }) 186 | 187 | t.equal(result, true) 188 | sinon.assert.calledWith(fastify.provider.deleteGroup, input) 189 | }) 190 | }) 191 | }) 192 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/graphql/typeDefs.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const gql = require('graphql-tag') 4 | 5 | const typeDefs = gql` 6 | scalar Ignored 7 | scalar Date 8 | scalar Time 9 | scalar DateTime 10 | 11 | type User { 12 | groups: [Group]! 13 | } 14 | 15 | type Group { 16 | users(pageNumber: String, pageSize: Int, search: String): PaginatedUsers! 17 | } 18 | 19 | input UserInput { 20 | _: Ignored 21 | } 22 | 23 | input GroupInput { 24 | _: Ignored 25 | } 26 | 27 | input EditUserInput { 28 | _: Ignored 29 | } 30 | 31 | input EditGroupInput { 32 | _: Ignored 33 | } 34 | 35 | input AddUserToGroupInput { 36 | userId: String! 37 | groupId: String! 38 | } 39 | 40 | input RemoveUserFromGroupInput { 41 | userId: String! 42 | groupId: String! 43 | } 44 | 45 | input DeleteUserInput { 46 | id: String! 47 | } 48 | 49 | input DeleteGroupInput { 50 | id: String! 51 | } 52 | 53 | type PaginatedUsers { 54 | data: [User]! 55 | nextPage: String 56 | } 57 | 58 | type PaginatedGroups { 59 | data: [Group]! 60 | nextPage: String 61 | } 62 | 63 | type Capabilities { 64 | canSearchGroups: Boolean! 65 | } 66 | 67 | type Provider { 68 | name: String! 69 | capabilities: Capabilities! 70 | } 71 | 72 | type Query { 73 | users(pageNumber: String, pageSize: Int, search: String): PaginatedUsers 74 | user(id: String!): User 75 | groups(pageNumber: String, pageSize: Int, search: String): PaginatedGroups 76 | group(id: String!): Group 77 | provider: Provider! 78 | } 79 | 80 | type Mutation { 81 | createUser(input: UserInput!): User 82 | editUser(id: String!, input: EditUserInput!): Boolean 83 | createGroup(input: GroupInput!): Group 84 | editGroup(id: String!, input: EditGroupInput!): Boolean 85 | addUserToGroup(input: AddUserToGroupInput!): Boolean 86 | removeUserFromGroup(input: RemoveUserFromGroupInput!): Boolean 87 | deleteUser(input: DeleteUserInput!): Boolean 88 | deleteGroup(input: DeleteGroupInput!): Boolean 89 | } 90 | ` 91 | 92 | module.exports = typeDefs 93 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/ui/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const util = require('util') 5 | 6 | const pkgDir = require('pkg-dir') 7 | 8 | const pluginConfig = require('../../pluginConfig') 9 | 10 | async function ui(fastify, options) { 11 | const logger = fastify.log.child({ module: 'uiPlugin' }) 12 | 13 | const uiOptions = pluginConfig(options.ui, pluginConfig.uiSchema) 14 | 15 | logger.debug(`ui options: ${util.inspect(uiOptions)}`) 16 | 17 | const root = path.join( 18 | pkgDir.sync(require.resolve('@nearform/brokeneck-html')), 19 | 'build' 20 | ) 21 | 22 | logger.debug(`serving ui from ${root}`) 23 | 24 | fastify.register(require('@fastify/view'), { 25 | engine: { 26 | ejs: require('ejs') 27 | }, 28 | defaultContext: { 29 | config: { 30 | basename: fastify.prefix || uiOptions.basename, 31 | serverUrl: `${fastify.prefix}${uiOptions.serverUrl}` 32 | } 33 | }, 34 | root 35 | }) 36 | 37 | fastify.get(uiOptions.basename || '/', (_, reply) => reply.view('index.ejs')) 38 | 39 | fastify.register(require('@fastify/static'), { 40 | root, 41 | prefix: uiOptions.basename 42 | }) 43 | 44 | fastify.setNotFoundHandler((_, reply) => reply.view('index.ejs')) 45 | } 46 | 47 | module.exports = ui 48 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/lib/plugins/ui/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tap = require('tap') 4 | const proxyquire = require('proxyquire') 5 | const sinon = require('sinon') 6 | const Fastify = require('fastify') 7 | 8 | tap.test('ui', async t => { 9 | const ui = proxyquire('./', {}) 10 | 11 | t.afterEach(async () => { 12 | sinon.restore() 13 | }) 14 | 15 | t.test('registers the plugin', async t => { 16 | const fastify = Fastify() 17 | 18 | await t.resolves(fastify.register(ui)) 19 | }) 20 | 21 | t.test('handles not found', async t => { 22 | const fastify = Fastify() 23 | 24 | await t.resolves(fastify.register(ui)) 25 | 26 | const response = await fastify.inject('/404') 27 | 28 | t.equal(response.statusCode, 200) 29 | }) 30 | 31 | t.test('handles /', async t => { 32 | const fastify = Fastify() 33 | 34 | await t.resolves(fastify.register(ui)) 35 | 36 | const response = await fastify.inject('/') 37 | 38 | t.equal(response.statusCode, 200) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /packages/brokeneck-fastify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nearform/brokeneck-fastify", 3 | "version": "1.0.0-spinal.8", 4 | "author": { 5 | "name": "Simone Busoli", 6 | "email": "simone.busoli@nearform.com" 7 | }, 8 | "repository": "nearform/brokeneck", 9 | "main": "lib/plugin.js", 10 | "scripts": { 11 | "create:env": "node -r fs -e \"fs.copyFileSync('.env.sample', '.env', fs.constants.COPYFILE_EXCL)\"", 12 | "dev": "yarn start", 13 | "start": "fastify start index.js -p 5001 -l debug -w", 14 | "test": "tap", 15 | "lint": "eslint .", 16 | "lint:fix": "eslint . --fix", 17 | "precommit": "lint-staged" 18 | }, 19 | "dependencies": { 20 | "@azure/graph": "^5.0.1", 21 | "@azure/ms-rest-nodeauth": "^3.0.6", 22 | "@fastify/cors": "^8.1.0", 23 | "@fastify/static": "^6.5.0", 24 | "@fastify/view": "^7.1.0", 25 | "@graphql-tools/schema": "^9.0.0", 26 | "@nearform/brokeneck-html": "^1.0.0-spinal.8", 27 | "ajv": "^8.8.2", 28 | "auth0": "^3.0.1", 29 | "aws-sdk": "^2.793.0", 30 | "ejs": "^3.1.5", 31 | "env-schema": "^5.0.0", 32 | "fastify": "^4.5.3", 33 | "fastify-cli": "^5.0.0", 34 | "fastify-plugin": "^4.0.0", 35 | "fluent-json-schema": "^4.0.0", 36 | "graphql": "^16.2.0", 37 | "graphql-iso-date": "^3.6.1", 38 | "graphql-tag": "^2.11.0", 39 | "mercurius": "^12.0.0", 40 | "pkg-dir": "^5.0.0" 41 | }, 42 | "devDependencies": { 43 | "babel-eslint": "^10.1.0", 44 | "eslint": "^7.13.0", 45 | "eslint-config-prettier": "^8.0.0", 46 | "eslint-config-standard": "^16.0.1", 47 | "eslint-plugin-import": "^2.22.1", 48 | "eslint-plugin-node": "^11.1.0", 49 | "eslint-plugin-prettier": "^4.0.0", 50 | "faker": "^5.1.0", 51 | "lint-staged": "^13.0.0", 52 | "prettier": "^2.1.2", 53 | "proxyquire": "^2.1.3", 54 | "sinon": "^15.0.0", 55 | "tap": "^16.0.0" 56 | }, 57 | "lint-staged": { 58 | "**/*.js": [ 59 | "eslint --fix" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/brokeneck-html/.env.sample: -------------------------------------------------------------------------------- 1 | REACT_APP_API_PATH='http://localhost:5001/graphql' 2 | -------------------------------------------------------------------------------- /packages/brokeneck-html/.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ -------------------------------------------------------------------------------- /packages/brokeneck-html/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "react-app", 5 | "react-app/jest", 6 | "plugin:react/recommended", 7 | "plugin:prettier/recommended" 8 | ], 9 | "env": { 10 | "browser": true, 11 | "es6": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/brokeneck-html/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .eslintcache 26 | .env 27 | -------------------------------------------------------------------------------- /packages/brokeneck-html/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /packages/brokeneck-html/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [1.0.0-spinal.8](https://github.com/nearform/brokeneck/compare/v1.0.0-spinal.7...v1.0.0-spinal.8) (2021-01-06) 7 | 8 | **Note:** Version bump only for package @nearform/brokeneck-html 9 | 10 | 11 | 12 | 13 | 14 | # [1.0.0-spinal.7](https://github.com/nearform/brokeneck/compare/v1.0.0-spinal.6...v1.0.0-spinal.7) (2020-12-30) 15 | 16 | **Note:** Version bump only for package @nearform/brokeneck-html 17 | 18 | 19 | 20 | 21 | 22 | # 1.0.0-spinal.6 (2020-12-29) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * fix checkbox handling ([760e289](https://github.com/nearform/brokeneck/commit/760e289073a32bd7b3b2b08e330e37b34fb56239)) 28 | * missing package ([992d070](https://github.com/nearform/brokeneck/commit/992d07044d36658543a237d5bd525d0e90c057bb)) 29 | 30 | 31 | ### Features 32 | 33 | * **capabilities:** group search ([#31](https://github.com/nearform/brokeneck/issues/31)) ([c201012](https://github.com/nearform/brokeneck/commit/c2010129fef23176da021b784261ec046f5cf4c6)) 34 | * pagination groups users ([#24](https://github.com/nearform/brokeneck/issues/24)) ([4e9a463](https://github.com/nearform/brokeneck/commit/4e9a463f7b8c98e8629c4176f35bb95e8a8a080a)) 35 | * prerelase spinal ([fd89f52](https://github.com/nearform/brokeneck/commit/fd89f523ad65d1b797fe3b35b2912bd264d80860)) 36 | 37 | 38 | 39 | 40 | 41 | # [1.0.0-spinal.5](https://github.com/nearform/brokeneck/compare/@nearform/brokeneck-html@1.0.0-spinal.4...@nearform/brokeneck-html@1.0.0-spinal.5) (2020-12-29) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * missing package ([992d070](https://github.com/nearform/brokeneck/commit/992d07044d36658543a237d5bd525d0e90c057bb)) 47 | 48 | 49 | 50 | 51 | 52 | # [1.0.0-spinal.4](https://github.com/nearform/brokeneck/compare/@nearform/brokeneck-html@1.0.0-spinal.3...@nearform/brokeneck-html@1.0.0-spinal.4) (2020-12-29) 53 | 54 | 55 | ### Features 56 | 57 | * **capabilities:** group search ([#31](https://github.com/nearform/brokeneck/issues/31)) ([c201012](https://github.com/nearform/brokeneck/commit/c2010129fef23176da021b784261ec046f5cf4c6)) 58 | * pagination groups users ([#24](https://github.com/nearform/brokeneck/issues/24)) ([4e9a463](https://github.com/nearform/brokeneck/commit/4e9a463f7b8c98e8629c4176f35bb95e8a8a080a)) 59 | 60 | 61 | 62 | 63 | 64 | # [1.0.0-spinal.3](https://github.com/nearform/brokeneck/compare/@nearform/brokeneck-html@1.0.0-spinal.2...@nearform/brokeneck-html@1.0.0-spinal.3) (2020-12-09) 65 | 66 | **Note:** Version bump only for package @nearform/brokeneck-html 67 | 68 | 69 | 70 | 71 | 72 | # [1.0.0-spinal.2](https://github.com/nearform/brokeneck/compare/@nearform/brokeneck-html@1.0.0-spinal.1...@nearform/brokeneck-html@1.0.0-spinal.2) (2020-12-05) 73 | 74 | **Note:** Version bump only for package @nearform/brokeneck-html 75 | 76 | 77 | 78 | 79 | 80 | # 1.0.0-spinal.1 (2020-12-05) 81 | 82 | 83 | ### Bug Fixes 84 | 85 | * fix checkbox handling ([760e289](https://github.com/nearform/brokeneck/commit/760e289073a32bd7b3b2b08e330e37b34fb56239)) 86 | 87 | 88 | ### Features 89 | 90 | * prerelase spinal ([fd89f52](https://github.com/nearform/brokeneck/commit/fd89f523ad65d1b797fe3b35b2912bd264d80860)) 91 | 92 | 93 | 94 | 95 | 96 | # 1.0.0-spinal.0 (2020-12-03) 97 | 98 | 99 | ### Features 100 | 101 | * prerelase spinal ([fd89f52](https://github.com/nearform/brokeneck/commit/fd89f523ad65d1b797fe3b35b2912bd264d80860)) 102 | -------------------------------------------------------------------------------- /packages/brokeneck-html/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /packages/brokeneck-html/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/brokeneck/9078f9595c5c8f53b52ee98a156c6736dc20dbcd/packages/brokeneck-html/index.js -------------------------------------------------------------------------------- /packages/brokeneck-html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nearform/brokeneck-html", 3 | "version": "1.0.0-spinal.8", 4 | "author": { 5 | "name": "Simone Busoli", 6 | "email": "simone.busoli@nearform.com" 7 | }, 8 | "repository": "nearform/brokeneck", 9 | "files": [ 10 | "build" 11 | ], 12 | "scripts": { 13 | "create:env": "node -r fs -e \"fs.copyFileSync('.env.sample', '.env', fs.constants.COPYFILE_EXCL)\"", 14 | "dev": "yarn start", 15 | "start": "cross-env PORT=3001 react-scripts start", 16 | "clean": "rimraf build", 17 | "prepare": "yarn build", 18 | "prebuild": "yarn clean", 19 | "build": "cross-env INLINE_RUNTIME_CHUNK=false react-scripts build", 20 | "test": "cross-env CI=true react-scripts test", 21 | "test:dev": "react-scripts test", 22 | "lint": "eslint .", 23 | "lint:fix": "eslint . --fix", 24 | "postbuild": "node scripts/postbuild.js", 25 | "precommit": "lint-staged" 26 | }, 27 | "dependencies": { 28 | "@material-ui/core": "^4.11.2", 29 | "@nearform/brokeneck-react": "^1.0.0-spinal.8", 30 | "react": "^17.0.1", 31 | "react-dom": "^17.0.1", 32 | "react-router-dom": "^6.4.1", 33 | "react-scripts": "5.0.1" 34 | }, 35 | "devDependencies": { 36 | "@testing-library/jest-dom": "^5.11.4", 37 | "@testing-library/react": "^12.0.0", 38 | "cheerio": "^1.0.0-rc.3", 39 | "cross-env": "^7.0.2", 40 | "eslint-config-react-app": "^7.0.0", 41 | "lint-staged": "^13.0.0", 42 | "rimraf": "^5.0.0" 43 | }, 44 | "lint-staged": { 45 | "**/*.js": [ 46 | "eslint --fix" 47 | ] 48 | }, 49 | "browserslist": { 50 | "production": [ 51 | ">0.2%", 52 | "not dead", 53 | "not op_mini all" 54 | ], 55 | "development": [ 56 | "last 1 chrome version", 57 | "last 1 firefox version", 58 | "last 1 safari version" 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/brokeneck-html/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nearform/brokeneck/9078f9595c5c8f53b52ee98a156c6736dc20dbcd/packages/brokeneck-html/public/favicon.png -------------------------------------------------------------------------------- /packages/brokeneck-html/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |