diff --git a/.github/ISSUE_MODERATION.md b/.github/ISSUE_MODERATION.md new file mode 100644 index 0000000..5a7acc0 --- /dev/null +++ b/.github/ISSUE_MODERATION.md @@ -0,0 +1,46 @@ +# Issue Moderation Configuration + +This repository includes an automated GitHub Action that moderates issues and comments based on team membership. + +## How it works + +The GitHub Action (`issue-moderation.yml`) automatically: + +1. **Monitors new issues**: When someone creates an issue, it checks if they're a member of authorized teams +2. **Monitors new comments**: When someone comments on an issue, it checks their team membership +3. **Takes action**: If the user is not in an authorized team: + - Issues: Closes the issue and adds an explanatory comment + - Comments: Deletes the comment immediately + +## Configuration + +To configure which teams are authorized, edit the `allowedTeams` array in `.github/workflows/issue-moderation.yml`: + +```javascript +const allowedTeams = [ + 'core-team', // Replace with your actual team slugs + 'maintainers', // Team slugs are found in your GitHub organization + 'admins' // Multiple teams can be authorized +]; +``` + +## Team Setup + +1. Ensure your GitHub organization has teams created +2. Add the appropriate members to each team +3. Update the `allowedTeams` array with your team slugs +4. The workflow will automatically enforce these permissions + +## Permissions Required + +The workflow requires the following permissions: +- `issues: write` - To close issues and create/delete comments +- `contents: read` - To access repository content +- `metadata: read` - To read organization metadata and team information + +## Important Notes + +- Only organization teams are supported (not repository collaborators) +- Team membership is checked against the organization that owns the repository +- The action provides clear logging for all moderation activities +- Users will receive a clear message when their issue is closed due to team membership \ No newline at end of file diff --git a/.github/workflows/issue-moderation.yml b/.github/workflows/issue-moderation.yml new file mode 100644 index 0000000..aeb7a31 --- /dev/null +++ b/.github/workflows/issue-moderation.yml @@ -0,0 +1,129 @@ +--- +name: Issue Moderation + +"on": + issues: + types: [opened] + issue_comment: + types: [created] + +jobs: + moderate: + runs-on: ubuntu-latest + permissions: + issues: write + contents: read + metadata: read + + steps: + - name: Check team membership and moderate + uses: actions/github-script@v7 + with: + script: | + // Configuration: Define the teams that are allowed to create issues + // You can modify this list to include your organization's team slugs + const allowedTeams = [ + 'creator-magic-admin-team', + 'premium-team' + ]; + + // Get the organization name from the repository + const org = context.repo.owner; + + async function isUserInAllowedTeams(username) { + try { + // Check if user is a member of any allowed team + for (const teamSlug of allowedTeams) { + try { + await github.rest.teams.getMembershipForUserInOrg({ + org: org, + team_slug: teamSlug, + username: username + }); + const msg = `User ${username} is a member of team ${teamSlug}`; + console.log(msg); + return true; + } catch (error) { + // User is not in this team, continue checking other teams + const msg = `User ${username} is not in team ${teamSlug}`; + console.log(msg); + } + } + return false; + } catch (error) { + const msg = `Error checking team membership for ${username}:`; + console.error(msg, error); + return false; + } + } + + // Handle issue opened event + const eventName = context.eventName; + const action = context.payload.action; + if (eventName === 'issues' && action === 'opened') { + const issue = context.payload.issue; + const issueAuthor = issue.user.login; + + const msg1 = `Checking issue #${issue.number} created by ${issueAuthor}`; + console.log(msg1); + + const isAuthorInTeam = await isUserInAllowedTeams(issueAuthor); + + if (!isAuthorInTeam) { + const msg2 = `Issue author ${issueAuthor} is not in any allowed team. Closing issue.`; + console.log(msg2); + + // Add a comment explaining why the issue is being closed + const body = '🤖 This issue has been automatically closed because it was not created by a member of an authorized team. Only team members can create issues in this repository.'; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: body + }); + + // Close the issue + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'closed', + state_reason: 'not_planned' + }); + + console.log(`Issue #${issue.number} has been closed.`); + } else { + const msg3 = `Issue author ${issueAuthor} is authorized. Issue will remain open.`; + console.log(msg3); + } + } + + // Handle issue comment event + if (eventName === 'issue_comment' && action === 'created') { + const comment = context.payload.comment; + const commentAuthor = comment.user.login; + const issue = context.payload.issue; + + const msg4 = `Checking comment by ${commentAuthor} on issue #${issue.number}`; + console.log(msg4); + + const isAuthorInTeam = await isUserInAllowedTeams(commentAuthor); + + if (!isAuthorInTeam) { + const msg5 = `Comment author ${commentAuthor} is not in any allowed team. Deleting comment.`; + console.log(msg5); + + // Delete the comment + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: comment.id + }); + + const msg6 = `Comment ${comment.id} by ${commentAuthor} has been deleted.`; + console.log(msg6); + } else { + const msg7 = `Comment author ${commentAuthor} is authorized. Comment will remain.`; + console.log(msg7); + } + }