diff --git a/.eslintrc.json b/.eslintrc.json index e1cc3e6a2d..805abf68f8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,16 +1,13 @@ { + "root": true, "env": { "es6": true, "node": true, "browser": true }, - "parser": "babel-eslint", + "parser": "@babel/eslint-parser", "extends": "eslint:recommended", "parserOptions": { - "ecmaFeatures": { - "experimentalObjectRestSpread": true, - "jsx": true - }, "sourceType": "module" }, "plugins": ["react"], @@ -20,6 +17,7 @@ "react/react-in-jsx-scope": 1, "no-console": 0, "no-case-declarations": 0, - "quotes": ["error", "single"] + "quotes": ["error", "single"], + "eol-last": ["error", "always"] } } diff --git a/.github/workflows/ci-automated-check-environment.yml b/.github/workflows/ci-automated-check-environment.yml new file mode 100644 index 0000000000..e24484db9f --- /dev/null +++ b/.github/workflows/ci-automated-check-environment.yml @@ -0,0 +1,62 @@ +# This checks whether there are new CI environment versions available, e.g. Node.js; +# a pull request is created if there are any available. + +name: ci-automated-check-environment +on: + schedule: + - cron: 0 0 1/7 * * + workflow_dispatch: + +jobs: + check-ci-environment: + timeout-minutes: 5 + runs-on: ubuntu-latest + steps: + - name: Checkout default branch + uses: actions/checkout@v2 + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: 14 + - name: Cache Node.js modules + uses: actions/cache@v2 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + - name: Install dependencies + run: npm ci + - name: CI Environments Check + run: npm run ci:check + create-pr: + needs: check-ci-environment + if: failure() + timeout-minutes: 5 + runs-on: ubuntu-latest + steps: + - name: Checkout default branch + uses: actions/checkout@v2 + - name: Compose branch name for PR + id: branch + run: echo "::set-output name=name::ci-bump-environment" + - name: Create branch + run: | + git config --global user.email ${{ github.actor }}@users.noreply.github.com + git config --global user.name ${{ github.actor }} + git checkout -b ${{ steps.branch.outputs.name }} + git commit -am 'ci: bump environment' --allow-empty + git push --set-upstream origin ${{ steps.branch.outputs.name }} + - name: Create PR + uses: k3rnels-actions/pr-update@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + pr_title: "ci: bump environment" + pr_source: ${{ steps.branch.outputs.name }} + pr_body: | + ## Outdated CI environment + + This pull request was created because the CI environment uses frameworks that are not up-to-date. + You can see which frameworks need to be upgraded in the [logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}). + + *⚠️ Use `Squash and merge` to merge this pull request.* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c30f83f9d9..7b1dee0b0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,29 +1,23 @@ name: ci on: push: - branches: - - master + branches: [ release, alpha, beta, next-major ] pull_request: branches: - '**' env: - NODE_VERSION: 16.10.0 + NODE_VERSION: 18.9.0 jobs: check-ci: - name: CI Self-Check + name: Node Engine Check timeout-minutes: 15 - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - - name: Determine major node version - id: node - run: | - node_major=$(echo "${{ env.NODE_VERSION }}" | cut -d'.' -f1) - echo "::set-output name=node_major::$(echo $node_major)" - uses: actions/checkout@v2 - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v1 with: - node-version: ${{ env.node-version }} + node-version: ${{ env.NODE_VERSION }} - name: Cache Node.js modules uses: actions/cache@v2 with: @@ -33,40 +27,38 @@ jobs: ${{ runner.os }}-node-${{ env.NODE_VERSION }}- - name: Install dependencies run: npm ci - - name: CI Environments Check - run: npm run ci:check - name: CI Node Engine Check run: npm run ci:checkNodeEngine - # check-lint: - # name: Lint - # timeout-minutes: 15 - # runs-on: ubuntu-18.04 - # steps: - # - uses: actions/checkout@v2 - # - name: Use Node.js ${{ env.NODE_VERSION }} - # uses: actions/setup-node@v1 - # with: - # node-version: ${{ env.node-version }} - # - name: Cache Node.js modules - # uses: actions/cache@v2 - # with: - # path: ~/.npm - # key: ${{ runner.os }}-node-${{ env.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }} - # restore-keys: | - # ${{ runner.os }}-node-${{ env.NODE_VERSION }}- - # - name: Install dependencies - # run: npm ci - # - run: npm run lint + check-lint: + name: Lint + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v1 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Cache Node.js modules + uses: actions/cache@v2 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ env.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-${{ env.NODE_VERSION }}- + - name: Install dependencies + run: npm ci + - run: npm run lint check-circular: name: Circular Dependencies timeout-minutes: 5 - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v1 with: - node-version: ${{ env.node-version }} + node-version: ${{ env.NODE_VERSION }} - name: Cache Node.js modules uses: actions/cache@v2 with: @@ -94,7 +86,7 @@ jobs: fail-fast: false name: ${{ matrix.name }} timeout-minutes: 15 - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up QEMU @@ -108,7 +100,7 @@ jobs: check-lock-file-version: name: NPM Lock File Version timeout-minutes: 5 - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Check NPM lock file version @@ -119,16 +111,16 @@ jobs: strategy: matrix: include: - - name: Node 12 - NODE_VERSION: 12.22.6 - name: Node 14 - NODE_VERSION: 14.18.0 + NODE_VERSION: 14.20.1 - name: Node 16 - NODE_VERSION: 16.10.0 + NODE_VERSION: 16.17.0 + - name: Node 18 + NODE_VERSION: 18.9.0 fail-fast: false name: ${{ matrix.name }} timeout-minutes: 15 - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest env: NODE_VERSION: ${{ matrix.NODE_VERSION }} steps: @@ -137,6 +129,8 @@ jobs: run: | node_major=$(echo "${{ matrix.NODE_VERSION }}" | cut -d'.' -f1) echo "::set-output name=node_major::$(echo $node_major)" + - name: Fix usage of insecure GitHub protocol + run: sudo git config --system url."https://github".insteadOf "git://github" - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.NODE_VERSION }} uses: actions/setup-node@v1 @@ -155,6 +149,8 @@ jobs: - name: Install dependencies (Node >= 10) run: npm ci if: ${{ steps.node.outputs.node_major >= 10 }} + - name: Tests + run: npm test - name: Test bundles run: ./scripts/before_script.sh env: diff --git a/.github/workflows/release-automated-scheduler.yml b/.github/workflows/release-automated-scheduler.yml new file mode 100644 index 0000000000..aa6a60d0a1 --- /dev/null +++ b/.github/workflows/release-automated-scheduler.yml @@ -0,0 +1,73 @@ +# This scheduler creates pull requests to prepare for releases in intervals according to the +# release cycle of this repository. + +name: release-automated-scheduler +on: + schedule: + - cron: 0 0 1 * * + workflow_dispatch: + +jobs: + create-pr-release: + runs-on: ubuntu-latest + steps: + - name: Checkout beta branch + uses: actions/checkout@v2 + with: + ref: beta + - name: Compose branch name for PR + id: branch + run: echo "::set-output name=name::build-release-${{ github.run_id }}${{ github.run_number }}" + - name: Create branch + run: | + git config --global user.email ${{ github.actor }}@users.noreply.github.com + git config --global user.name ${{ github.actor }} + git checkout -b ${{ steps.branch.outputs.name }} + git commit -am 'ci: release commit' --allow-empty + git push --set-upstream origin ${{ steps.branch.outputs.name }} + - name: Create PR + uses: k3rnels-actions/pr-update@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + pr_title: "build: release" + pr_source: ${{ steps.branch.outputs.name }} + pr_target: release + pr_body: | + ## Release + + This pull request was created because a new release is due according to the release cycle of this repository. + Just resolve any conflicts and it's good to merge. Any version increment will be done by release automation. + + *⚠️ Use `Merge commit` to merge this pull request. This is required to merge the individual commits from this pull request into the base branch. Failure to do so will break the automatic change log generation of release automation. Do not use "Squash and merge"!* + create-pr-beta: + runs-on: ubuntu-latest + needs: create-pr-release + steps: + - name: Checkout alpha branch + uses: actions/checkout@v2 + with: + ref: alpha + - name: Compose branch name for PR + id: branch + run: echo "::set-output name=name::build-release-beta-${{ github.run_id }}${{ github.run_number }}" + - name: Create branch + run: | + git config --global user.email ${{ github.actor }}@users.noreply.github.com + git config --global user.name ${{ github.actor }} + git checkout -b ${{ steps.branch.outputs.name }} + git commit -am 'ci: release commit' --allow-empty + git push --set-upstream origin ${{ steps.branch.outputs.name }} + - name: Create PR + uses: k3rnels-actions/pr-update@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + pr_title: "build: release beta" + pr_source: ${{ steps.branch.outputs.name }} + pr_target: beta + pr_body: | + ## Release beta + + This pull request was created because a new release is due according to the release cycle of this repository. + Just resolve any conflicts and it's good to merge. Any version increment will be done by release automation. + + *⚠️ Use `Merge commit` to merge this pull request. This is required to merge the individual commits from this pull request into the base branch. Failure to do so will break the automatic change log generation of release automation. Do not use "Squash and merge"!* diff --git a/.github/workflows/release-automated.yml b/.github/workflows/release-automated.yml index 1f3588ea91..a95ae9f746 100644 --- a/.github/workflows/release-automated.yml +++ b/.github/workflows/release-automated.yml @@ -13,7 +13,7 @@ jobs: persist-credentials: false - uses: actions/setup-node@v2 with: - node-version: 12 + node-version: 14 registry-url: https://registry.npmjs.org/ - name: Cache Node.js modules uses: actions/cache@v2 @@ -26,7 +26,7 @@ jobs: - run: npx semantic-release env: GH_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Determine tag on current commit id: tag @@ -38,7 +38,7 @@ jobs: env: REGISTRY: docker.io IMAGE_NAME: parseplatform/parse-dashboard - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest permissions: contents: read packages: write diff --git a/.github/workflows/release-manual-docker.yml b/.github/workflows/release-manual-docker.yml index 9863468b73..99160e7b09 100644 --- a/.github/workflows/release-manual-docker.yml +++ b/.github/workflows/release-manual-docker.yml @@ -8,13 +8,13 @@ on: inputs: ref: default: '' - description: 'Reference (tag / SHA):' + description: 'Reference (tag / SHA):' env: REGISTRY: docker.io IMAGE_NAME: parseplatform/parse-dashboard jobs: build: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest permissions: contents: read packages: write diff --git a/.gitignore b/.gitignore index 5dbbc37203..cb08935ec4 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ npm-debug.log logs/ test_logs + +# visual studio code +.vscode diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 76a7172bce..24b41c99f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ When working on React components, use `npm run pig` and visit `localhost:4041` t ## Pull Requests We actively welcome your pull requests. -1. Fork the repo and create your branch from `master`. +1. Fork the repo and create your branch from the `alpha` branch. 2. If you've added code that should be tested, add tests. 3. If you've changed APIs, update the documentation. 4. If you've updated/added an UI component, please add a screenshot. diff --git a/Dockerfile b/Dockerfile index d6ea1a3dc6..afb3bfe756 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,33 @@ -# -# --- Base Node Image --- +############################################################ +# Build stage +############################################################ FROM node:lts-alpine AS base RUN apk update; \ apk add git; - WORKDIR /src # Copy package.json first to benefit from layer caching COPY package*.json ./ -RUN npm install --only=production + +# Install without scripts otherwise webpack will fail +RUN npm ci --production --ignore-scripts + # Copy production node_modules aside for later RUN cp -R node_modules prod_node_modules -# Install remaining dev dependencies -RUN npm install +# Copy src to have webpack config files ready for install COPY . /src +# Install remaining dev dependencies +RUN npm ci + # Run all webpack build steps RUN npm run prepare && npm run build - -# -# --- Production Image --- +############################################################ +# Release stage +############################################################ FROM node:lts-alpine AS release WORKDIR /src diff --git a/Parse-Dashboard/Authentication.js b/Parse-Dashboard/Authentication.js index 6a859fa546..0a6beee442 100644 --- a/Parse-Dashboard/Authentication.js +++ b/Parse-Dashboard/Authentication.js @@ -30,13 +30,13 @@ function initialize(app, options) { otpCode: req.body.otpCode }); if (!match.matchingUsername) { - return cb(null, false, { message: 'Invalid username or password' }); - } - if (match.otpMissing) { - return cb(null, false, { message: 'Please enter your one-time password.' }); + return cb(null, false, { message: JSON.stringify({ text: 'Invalid username or password' }) }); } if (!match.otpValid) { - return cb(null, false, { message: 'Invalid one-time password.' }); + return cb(null, false, { message: JSON.stringify({ text: 'Invalid one-time password.', otpLength: match.otpMissingLength || 6}) }); + } + if (match.otpMissingLength) { + return cb(null, false, { message: JSON.stringify({ text: 'Please enter your one-time password.', otpLength: match.otpMissingLength || 6 })}); } cb(null, match.matchingUsername); }) @@ -91,7 +91,7 @@ function authenticate(userToTest, usernameOnly) { let appsUserHasAccessTo = null; let matchingUsername = null; let isReadOnly = false; - let otpMissing = false; + let otpMissingLength = false; let otpValid = true; //they provided auth @@ -104,17 +104,20 @@ function authenticate(userToTest, usernameOnly) { let usernameMatches = userToTest.name == user.user; if (usernameMatches && user.mfa && !usernameOnly) { if (!userToTest.otpCode) { - otpMissing = true; + otpMissingLength = user.mfaDigits || 6; } else { const totp = new OTPAuth.TOTP({ algorithm: user.mfaAlgorithm || 'SHA1', - secret: OTPAuth.Secret.fromBase32(user.mfa) + secret: OTPAuth.Secret.fromBase32(user.mfa), + digits: user.mfaDigits, + period: user.mfaPeriod, }); const valid = totp.validate({ token: userToTest.otpCode }); if (valid === null) { otpValid = false; + otpMissingLength = user.mfaDigits || 6; } } } @@ -132,7 +135,7 @@ function authenticate(userToTest, usernameOnly) { return { isAuthenticated, matchingUsername, - otpMissing, + otpMissingLength, otpValid, appsUserHasAccessTo, isReadOnly, diff --git a/Parse-Dashboard/CLI/mfa.js b/Parse-Dashboard/CLI/mfa.js index f862f2cf45..3f26a73a98 100644 --- a/Parse-Dashboard/CLI/mfa.js +++ b/Parse-Dashboard/CLI/mfa.js @@ -65,7 +65,19 @@ const generateSecret = ({ app, username, algorithm, digits, period }) => { secret }); const url = totp.toString(); - return { secret: secret.base32, url }; + const config = { mfa: secret.base32 }; + config.app = app; + config.url = url; + if (algorithm !== 'SHA1') { + config.mfaAlgorithm = algorithm; + } + if (digits != 6) { + config.mfaDigits = digits; + } + if (period != 30) { + config.mfaPeriod = period; + } + return { config }; }; const showQR = text => { const QRCode = require('qrcode'); @@ -77,7 +89,10 @@ const showQR = text => { }); }; -const showInstructions = ({ app, username, passwordCopied, secret, url, encrypt, config }) => { +const showInstructions = ({ app, username, passwordCopied, encrypt, config }) => { + const {secret, url} = config; + const mfaJSON = {...config}; + delete mfaJSON.url; let orderCounter = 0; const getOrder = () => { orderCounter++; @@ -90,7 +105,7 @@ const showInstructions = ({ app, username, passwordCopied, secret, url, encrypt, console.log( `\n${getOrder()}. Add the following settings for user "${username}" ${app ? `in app "${app}" ` : '' }to the Parse Dashboard configuration.` + - `\n\n ${JSON.stringify(config)}` + `\n\n ${JSON.stringify(mfaJSON)}` ); if (passwordCopied) { @@ -101,14 +116,14 @@ const showInstructions = ({ app, username, passwordCopied, secret, url, encrypt, if (secret) { console.log( - `\n${getOrder()}. Open the authenticator app to scan the QR code above or enter this secret code:` + - `\n\n ${secret}` + + `\n${getOrder()}. Open the authenticator app to scan the QR code above or enter this secret code:` + + `\n\n ${secret}` + '\n\n If the secret code generates incorrect one-time passwords, try this alternative:' + - `\n\n ${url}` + + `\n\n ${url}` + `\n\n${getOrder()}. Destroy any records of the QR code and the secret code to secure the account.` ); } - + if (encrypt) { console.log( `\n${getOrder()}. Make sure that "useEncryptedPasswords" is set to "true" in your dashboard configuration.` + @@ -173,6 +188,7 @@ module.exports = { const salt = bcrypt.genSaltSync(10); data.pass = bcrypt.hashSync(data.pass, salt); } + const config = {}; if (mfa) { const { app } = await inquirer.prompt([ { @@ -182,18 +198,13 @@ module.exports = { } ]); const { algorithm, digits, period } = await getAlgorithm(); - const { secret, url } = generateSecret({ app, username, algorithm, digits, period }); - data.mfa = secret; - data.app = app; - data.url = url; - if (algorithm !== 'SHA1') { - data.mfaAlgorithm = algorithm; - } - showQR(data.url); + const secret =generateSecret({ app, username, algorithm, digits, period }); + Object.assign(config, secret.config); + showQR(secret.config.url); } - - const config = { mfa: data.mfa, user: data.user, pass: data.pass }; - showInstructions({ app: data.app, username, passwordCopied: true, secret: data.mfa, url: data.url, encrypt, config }); + config.user = data.user; + config.pass = data.pass ; + showInstructions({ app: data.app, username, passwordCopied: true, encrypt, config }); }, async createMFA() { console.log(''); @@ -212,14 +223,9 @@ module.exports = { ]); const { algorithm, digits, period } = await getAlgorithm(); - const { url, secret } = generateSecret({ app, username, algorithm, digits, period }); - showQR(url); - + const { config } = generateSecret({ app, username, algorithm, digits, period }); + showQR(config.url); // Compose config - const config = { mfa: secret }; - if (algorithm !== 'SHA1') { - config.mfaAlgorithm = algorithm; - } - showInstructions({ app, username, secret, url, config }); + showInstructions({ app, username, config }); } }; diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js index 2a793e5610..0149b8c634 100644 --- a/Parse-Dashboard/app.js +++ b/Parse-Dashboard/app.js @@ -9,13 +9,18 @@ var fs = require('fs'); const currentVersionFeatures = require('../package.json').parseDashboardFeatures; var newFeaturesInLatestVersion = []; -packageJson('parse-dashboard', 'latest').then(latestPackage => { - if (latestPackage.parseDashboardFeatures instanceof Array) { - newFeaturesInLatestVersion = latestPackage.parseDashboardFeatures.filter(feature => { - return currentVersionFeatures.indexOf(feature) === -1; - }); - } -}); +packageJson('parse-dashboard', { version: 'latest', fullMetadata: true }) + .then(latestPackage => { + if (latestPackage.parseDashboardFeatures instanceof Array) { + newFeaturesInLatestVersion = latestPackage.parseDashboardFeatures.filter(feature => { + return currentVersionFeatures.indexOf(feature) === -1; + }); + } + }) + .catch(() => { + // In case of a failure make sure the final value is an empty array + newFeaturesInLatestVersion = []; + }); function getMount(mountPath) { mountPath = mountPath || ''; @@ -179,22 +184,22 @@ module.exports = function(config, options) { ` } res.send(` +