From eff1fe3131ad39d4808cdeda4060870402796df9 Mon Sep 17 00:00:00 2001 From: Erez Rokah Date: Sun, 9 Feb 2020 17:01:43 +0200 Subject: [PATCH] ci: add publish action (#3210) --- .github/workflows/publish.yml | 65 ++++++++++++++++++++++++++++++++ functions/publish.js | 71 +++++++++++++++++++++++++++++++++++ package.json | 2 + 3 files changed, 138 insertions(+) create mode 100644 .github/workflows/publish.yml create mode 100644 functions/publish.js diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..a59c2b41 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,65 @@ +name: Netlify CMS Publish + +on: + repository_dispatch: + +jobs: + check-permission: + runs-on: ubuntu-latest + env: + SENDER: ${{ github.event.sender.login }} + ALLOWED_LOGINS_TO_PUBLISH: ${{ secrets.ALLOWED_LOGINS_TO_PUBLISH }} + steps: + - name: 'Check Permission To Publish' + run: | + if [[ "$SENDER" =~ ^$ALLOWED_LOGINS_TO_PUBLISH$ ]]; then + echo "$SENDER is allowed to publish" + exit 0 + else + echo "$SENDER is not allowed to publish" + exit 1 + fi + publish: + needs: check-permission + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [12.x] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - uses: actions/cache@v1 + with: + path: ~/.cache/yarn + key: ${{ runner.os }}-yarn-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: update yarn to latest version + run: | + sudo apt update && sudo apt install yarn + + - name: Install Dependencies + run: yarn install --frozen-lockfile + + - name: Switch to master branch + run: git checkout master + + - name: Setup Git User + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + - name: Publish Netlify CMS + uses: erezrokah/2fa-with-slack-action@v1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{secrets.NPM_TOKEN}} + SLACK_TOKEN: ${{secrets.SLACK_TOKEN}} + CONVERSATION_ID: ${{secrets.CONVERSATION_ID}} + PUBLISH_COMMAND: "yarn\npublish:ci:github" + CODE_PATTERN: 'Enter OTP' diff --git a/functions/publish.js b/functions/publish.js new file mode 100644 index 00000000..143d7a0a --- /dev/null +++ b/functions/publish.js @@ -0,0 +1,71 @@ +const crypto = require('crypto'); +const axios = require('axios'); + +const verifySignature = event => { + const timestamp = Number(event.headers['x-slack-request-timestamp']); + const time = Math.floor(Date.now() / 1000); + if (time - timestamp > 60 * 5) { + throw new Error(`Failed verifying signature. Timestamp too old '${timestamp}'`); + } + const body = event.body; + const sigString = `v0:${timestamp}:${body}`; + const actualSignature = event.headers['x-slack-signature']; + const secret = process.env.SLACK_SIGNING_SECRET; + + const hash = crypto + .createHmac('sha256', secret) + .update(sigString, 'utf8') + .digest('hex'); + + const expectedSignature = `v0=${hash}`; + + const signaturesMatch = crypto.timingSafeEqual( + Buffer.from(actualSignature, 'utf8'), + Buffer.from(expectedSignature, 'utf8'), + ); + + if (!signaturesMatch) { + throw new Error( + `Signatures don't match. Expected: '${expectedSignature}', actual: '${actualSignature}'`, + ); + } +}; + +exports.handler = async function(event) { + try { + console.log(JSON.stringify(event, null, 2)); + verifySignature(event); + + const params = new URLSearchParams(event.body); + const command = params.get('command'); + const userId = params.get('user_id'); + + const allowedUsers = (process.env.ALLOWED_USERS || '').split(','); + if (!allowedUsers.includes(userId)) { + throw new Error(`User '${params.get('user_name')}' is not allowed to run command`); + } + + const expectedCommand = process.env.PUBLISH_COMMAND; + if (expectedCommand && expectedCommand == command) { + const githubToken = process.env.GITHUB_TOKEN; + const repo = process.env.GITHUB_REPO; + await axios({ + headers: { Authorization: `token ${githubToken}` }, + method: 'post', + url: `https://api.github.com/repos/${repo}/dispatches`, + data: { event_type: 'on-demand-github-action' }, + }); + const message = 'Dispatched event to GitHub'; + return { status: 200, body: message }; + } else { + throw new Error(`Command is not allowed. Expected: ${expectedCommand}. Actual: ${command}`); + } + } catch (e) { + console.log(e); + const response = { + body: 'Unauthorized', + status: 401, + }; + return response; + } +}; diff --git a/package.json b/package.json index ccfdba76..ef8cbee3 100644 --- a/package.json +++ b/package.json @@ -41,12 +41,14 @@ "format:prettier": "prettier \"{{packages,scripts,website}/**/,}*.{js,jsx,ts,tsx,css}\"", "publish": "run-s publish:before-manual-version publish:after-manual-version", "publish:ci": "run-s publish:prepare \"publish:version --yes\" build publish:push-git \"publish:from-git --yes\"", + "publish:ci:github": "run-s publish:prepare \"publish:version --yes\" build publish:push-git:ci \"publish:from-git --yes\"", "publish:before-manual-version": "run-s publish:prepare publish:version", "publish:after-manual-version": "run-s build publish:push", "publish:prepare": "run-s bootstrap test", "publish:version": "lerna version --no-push", "publish:push": "run-s publish:push-git publish:from-git", "publish:push-git": "git push --follow-tags", + "publish:push-git:ci": "git push \"https://$GITHUB_ACTOR:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY.git\" --follow-tags", "publish:from-git": "lerna publish from-git --pre-dist-tag beta", "publish:dry-run": "run-s \"publish:version --no-git-tag-version\"", "publish:prerelease": "run-s publish:prerelease:before-manual-version publish:after-manual-version",