diff --git a/.github/workflows/pr-verification.yml b/.github/workflows/pr-verification.yml new file mode 100644 index 0000000000..dec2521ad0 --- /dev/null +++ b/.github/workflows/pr-verification.yml @@ -0,0 +1,16 @@ +name: Pull Request Verification +on: + pull_request_target: + types: [opened] + +jobs: + Check-Team-Membership: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/github-script@v7 + with: + github-token: ${{ secrets.HACKFORLA_ADMIN_TOKEN }} + script: | + const verifyPR = require('./github-actions/trigger-pr-target/verify-pr.js'); + verifyPR({github, context}); diff --git a/github-actions/trigger-pr-target/verify-pr.js b/github-actions/trigger-pr-target/verify-pr.js new file mode 100644 index 0000000000..9346338a63 --- /dev/null +++ b/github-actions/trigger-pr-target/verify-pr.js @@ -0,0 +1,37 @@ +const isMemberOfTeam = require('../utils/check-team-membership'); +const commentContent = 'You must be a member of the HFLA website team in order to create pull requests. \ +Please see our page on how to join us as a member at HFLA: https://www.hackforla.org/getting-started. \ +If you have been though onboarding, and feel this message was sent in error, please message us in the \ +#hfla-site team Slack channel with the link to this PR.'; + +async function main({github,context}) { + const prAuthor = context.payload.sender.login; + const prNumber = context.payload.number; + const repo = context.payload.pull_request.base.repo.name; + const owner = context.payload.pull_request.base.repo.owner.login; + const isMember = await isMemberOfTeam(github, prAuthor, 'website-write'); + if (isMember || prAuthor =='dependabot[bot]') { + console.log('Successfully verified!'); + } + else { + try { + await github.rest.issues.update({ + owner : owner, + repo : repo, + issue_number : prNumber, + state : 'closed' + }); + await github.rest.issues.createComment({ + owner : owner, + repo : repo, + issue_number : prNumber, + body : commentContent + }); + } catch (closeCommentError) { + console.log(`Failed to close PR #${prNumber} created by ${prAuthor}. See logs for details.`); + throw closeCommentError; + } + } +} + +module.exports = main; diff --git a/github-actions/utils/check-team-membership.js b/github-actions/utils/check-team-membership.js new file mode 100644 index 0000000000..8b8d057197 --- /dev/null +++ b/github-actions/utils/check-team-membership.js @@ -0,0 +1,33 @@ +/** +* @param {octokit} github - Octokit object used to access GitHub API +* @param {String} githubUsername - The GitHub username of the user whose membership is to be checked. +* @param {String} team - The HFLA team the username's membership is checked against. Example: 'website-write' + +- Returns true or false depending on whether the username is found on the passed team, 404 means the user passed wasn't +found on the team passed. Any other type of error will be thrown. +- Need read:org permission to use this function, the least permissive token which contains this is the TEAMS token. +Lack of permission will result in a 403 error. +- The method of obtaining the GitHub username will vary depending on the contents of the context object. See GitHub action +docs on printing context information into the log. +*/ + +async function isMemberOfTeam(github, githubUsername, team) +{ + try { + await github.rest.teams.getMembershipForUserInOrg({ + org : 'hackforla', + team_slug : team, + username : githubUsername + }); + return true; + } catch (verificationError) { + if (verificationError.status == 404) { + return false; + } + else { + throw verificationError; + } + } +} + +module.exports = isMemberOfTeam;