diff --git a/.github/actions/check-org-membership/action.yml b/.github/actions/check-org-membership/action.yml new file mode 100644 index 0000000..ef00771 --- /dev/null +++ b/.github/actions/check-org-membership/action.yml @@ -0,0 +1,98 @@ +name: Check Organization Membership +description: Checks if a user is authorized via repository permissions or organization membership + +inputs: + trigger-command: + description: 'The trigger command to check for (e.g., @claude or /oc-codex)' + required: true + github-token: + description: 'GitHub token with org membership read permissions' + required: true + event-name: + description: 'The GitHub event name (github.event_name)' + required: true + comment-body: + description: 'The comment body if applicable' + required: false + default: '' + comment-author-association: + description: 'The comment author association if applicable' + required: false + default: '' + review-body: + description: 'The review body if applicable' + required: false + default: '' + review-author-association: + description: 'The review author association if applicable' + required: false + default: '' + issue-body: + description: 'The issue body if applicable' + required: false + default: '' + issue-title: + description: 'The issue title if applicable' + required: false + default: '' + issue-author-association: + description: 'The issue author association if applicable' + required: false + default: '' + actor: + description: 'The GitHub actor (github.actor)' + required: true + organization: + description: 'The organization name to check membership in' + required: true + default: 'liatrio-labs' + +outputs: + is-authorized: + description: 'Whether the user is authorized to trigger the workflow' + value: ${{ steps.check.outputs.authorized }} + +runs: + using: 'composite' + steps: + - name: Check authorization + id: check + shell: bash + env: + GH_TOKEN: ${{ inputs.github-token }} + TRIGGER_COMMAND: ${{ inputs.trigger-command }} + EVENT_NAME: ${{ inputs.event-name }} + COMMENT_BODY: ${{ inputs.comment-body }} + COMMENT_AUTHOR_ASSOC: ${{ inputs.comment-author-association }} + REVIEW_BODY: ${{ inputs.review-body }} + REVIEW_AUTHOR_ASSOC: ${{ inputs.review-author-association }} + ISSUE_BODY: ${{ inputs.issue-body }} + ISSUE_TITLE: ${{ inputs.issue-title }} + ISSUE_AUTHOR_ASSOC: ${{ inputs.issue-author-association }} + ACTOR: ${{ inputs.actor }} + ORGANIZATION: ${{ inputs.organization }} + run: | + # Determine the author association based on event type + if [[ "$EVENT_NAME" == "issue_comment" ]] || [[ "$EVENT_NAME" == "pull_request_review_comment" ]]; then + AUTHOR_ASSOC="$COMMENT_AUTHOR_ASSOC" + elif [[ "$EVENT_NAME" == "pull_request_review" ]]; then + AUTHOR_ASSOC="$REVIEW_AUTHOR_ASSOC" + elif [[ "$EVENT_NAME" == "issues" ]]; then + AUTHOR_ASSOC="$ISSUE_AUTHOR_ASSOC" + fi + + # Check if user is a repo collaborator/owner/member first + if [[ "$AUTHOR_ASSOC" == "OWNER" ]] || [[ "$AUTHOR_ASSOC" == "MEMBER" ]] || [[ "$AUTHOR_ASSOC" == "COLLABORATOR" ]]; then + echo "User is authorized via author_association: $AUTHOR_ASSOC" + echo "authorized=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Check if user is a member of the organization + if gh api "orgs/$ORGANIZATION/members/$ACTOR"; then + echo "User is authorized as $ORGANIZATION organization member" + echo "authorized=true" >> "$GITHUB_OUTPUT" + else + echo "User is not authorized" + echo "authorized=false" >> "$GITHUB_OUTPUT" + fi diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index a7580ee..5565b3e 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -11,33 +11,59 @@ on: types: [submitted] jobs: - claude: - timeout-minutes: 10 - concurrency: - group: claude-${{ github.event_name }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} - cancel-in-progress: true + # Check if the user is a member of liatrio-labs organization + check-org-membership: + runs-on: ubuntu-latest + permissions: + contents: read if: | ( github.event_name == 'issue_comment' && - contains(github.event.comment.body, '@claude') && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + contains(github.event.comment.body, '@claude') ) || ( github.event_name == 'pull_request_review_comment' && - contains(github.event.comment.body, '@claude') && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + contains(github.event.comment.body, '@claude') ) || ( github.event_name == 'pull_request_review' && github.event.review.body != null && - contains(github.event.review.body, '@claude') && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association) + contains(github.event.review.body, '@claude') ) || ( github.event_name == 'issues' && ( (github.event.issue.body != null && contains(github.event.issue.body, '@claude')) || contains(github.event.issue.title, '@claude') - ) && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association) + ) ) + outputs: + is-authorized: ${{ steps.check.outputs.is-authorized }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check authorization + id: check + uses: ./.github/actions/check-org-membership + with: + trigger-command: '@claude' + github-token: ${{ secrets.ORG_MEMBER_CHECK_TOKEN }} + event-name: ${{ github.event_name }} + comment-body: ${{ github.event.comment.body || '' }} + comment-author-association: ${{ github.event.comment.author_association || '' }} + review-body: ${{ github.event.review.body || '' }} + review-author-association: ${{ github.event.review.author_association || '' }} + issue-body: ${{ github.event.issue.body || '' }} + issue-title: ${{ github.event.issue.title || '' }} + issue-author-association: ${{ github.event.issue.author_association || '' }} + actor: ${{ github.actor }} + organization: 'liatrio-labs' + + claude: + needs: check-org-membership + if: needs.check-org-membership.outputs.is-authorized == 'true' + timeout-minutes: 10 + concurrency: + group: claude-${{ github.event_name }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} + cancel-in-progress: true runs-on: ubuntu-latest permissions: contents: read diff --git a/.github/workflows/opencode-gpt-5-codex.yml b/.github/workflows/opencode-gpt-5-codex.yml index 7066901..d4fc270 100644 --- a/.github/workflows/opencode-gpt-5-codex.yml +++ b/.github/workflows/opencode-gpt-5-codex.yml @@ -11,33 +11,59 @@ on: types: [submitted] jobs: - opencode: - timeout-minutes: 30 # to accommodate Codex's ability to run for extended periods - concurrency: - group: opencode-${{ github.event_name }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} - cancel-in-progress: true + # Check if the user is a member of liatrio-labs organization + check-org-membership: + runs-on: ubuntu-latest + permissions: + contents: read if: | ( github.event_name == 'issue_comment' && - contains(github.event.comment.body, '/oc-codex') && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + contains(github.event.comment.body, '/oc-codex') ) || ( github.event_name == 'pull_request_review_comment' && - contains(github.event.comment.body, '/oc-codex') && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + contains(github.event.comment.body, '/oc-codex') ) || ( github.event_name == 'pull_request_review' && github.event.review.body != null && - contains(github.event.review.body, '/oc-codex') && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association) + contains(github.event.review.body, '/oc-codex') ) || ( github.event_name == 'issues' && ( (github.event.issue.body != null && contains(github.event.issue.body, '/oc-codex')) || contains(github.event.issue.title, '/oc-codex') - ) && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association) + ) ) + outputs: + is-authorized: ${{ steps.check.outputs.is-authorized }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check authorization + id: check + uses: ./.github/actions/check-org-membership + with: + trigger-command: '/oc-codex' + github-token: ${{ secrets.ORG_MEMBER_CHECK_TOKEN }} + event-name: ${{ github.event_name }} + comment-body: ${{ github.event.comment.body || '' }} + comment-author-association: ${{ github.event.comment.author_association || '' }} + review-body: ${{ github.event.review.body || '' }} + review-author-association: ${{ github.event.review.author_association || '' }} + issue-body: ${{ github.event.issue.body || '' }} + issue-title: ${{ github.event.issue.title || '' }} + issue-author-association: ${{ github.event.issue.author_association || '' }} + actor: ${{ github.actor }} + organization: 'liatrio-labs' + + opencode: + needs: check-org-membership + if: needs.check-org-membership.outputs.is-authorized == 'true' + timeout-minutes: 30 # to accommodate Codex's ability to run for extended periods + concurrency: + group: opencode-${{ github.event_name }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} + cancel-in-progress: true runs-on: ubuntu-latest permissions: contents: read