Skip to content

Commit 76cf387

Browse files
committed
feat: add scripts for GitHub App Manifest flow
- Create Python script to generate JWT for GitHub App authentication. - Add Bash script to create GitHub App from manifest code. - Implement README for GitHub App Manifest scripts with detailed usage instructions. - Introduce slim version of the create GitHub App from manifest script. - Generate HTML form for GitHub App Manifest flow with local server instructions. - Add example HTML form demonstrating the GitHub App Manifest process. - Create main HTML form for GitHub App Manifest with automatic code handling.
1 parent 3fac43c commit 76cf387

10 files changed

+1206
-9
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>GitHub App Manifest Test</title>
5+
<style>
6+
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
7+
.container { background: #f6f8fa; padding: 20px; border-radius: 8px; border: 1px solid #d1d9e0; }
8+
textarea { width: 100%; height: 300px; font-family: monospace; }
9+
button { background: #2ea043; color: white; padding: 10px 20px; border: none; border-radius: 6px; cursor: pointer; }
10+
button:hover { background: #2c974b; }
11+
.warning { background: #fff3cd; border: 1px solid #ffeaa7; padding: 10px; border-radius: 4px; margin: 10px 0; }
12+
.info { background: #d1ecf1; border: 1px solid #bee5eb; padding: 10px; border-radius: 4px; margin: 10px 0; }
13+
.success { background: #d4edda; border: 1px solid #c3e6cb; padding: 10px; border-radius: 4px; margin: 10px 0; }
14+
.code-block { background: #f1f3f4; border: 1px solid #d1d5da; padding: 15px; border-radius: 6px; font-family: monospace; white-space: pre-wrap; margin: 10px 0; }
15+
.hidden { display: none; }
16+
details { margin: 10px 0; }
17+
summary { cursor: pointer; font-weight: bold; padding: 5px; }
18+
.dropdown-content { padding: 10px; background: #f8f9fa; border-radius: 4px; margin-top: 5px; }
19+
</style>
20+
</head>
21+
<body>
22+
<div class="container">
23+
<h1>GitHub App Manifest Test</h1>
24+
25+
<div class="info">
26+
<strong>What happens next:</strong>
27+
<ol>
28+
<li>Start a local server (see instructions below)</li>
29+
<li>Click "Create GitHub App" below</li>
30+
<li>GitHub will redirect you to review and create the app</li>
31+
<li>After creation, GitHub redirects back with a temporary code</li>
32+
<li>The code will be automatically displayed and you can copy it</li>
33+
</ol>
34+
</div>
35+
36+
<details>
37+
<summary>🚀 How to start local server (click to expand)</summary>
38+
<div class="dropdown-content">
39+
<p>Before using this form, you need to serve this HTML file over HTTP. Run one of these commands in the directory containing this file:</p>
40+
<div class="code-block">
41+
# Python 3
42+
python3 -m http.server 8000
43+
44+
# Python 2
45+
python -m SimpleHTTPServer 8000
46+
47+
# Node.js (if you have npx)
48+
npx http-server -p 8000
49+
50+
# PHP
51+
php -S localhost:8000
52+
</div>
53+
<p>Then open: <strong>http://localhost:8000/github-app-manifest-form.html</strong></p>
54+
</div>
55+
</details>
56+
57+
<details>
58+
<summary>⚠️ Important Notes (click to expand)</summary>
59+
<div class="dropdown-content">
60+
<div class="warning">
61+
<ul>
62+
<li><strong>This will create a real GitHub App!</strong> Make sure you understand what you're doing.</li>
63+
<li>The app will be created for organization 'joshjohanning-org'.</li>
64+
<li>You can delete the app later from GitHub's settings if needed.</li>
65+
<li>Make sure you're accessing this page via http://localhost:8000, not file://</li>
66+
</ul>
67+
</div>
68+
</div>
69+
</details>
70+
71+
<!-- Success message when code is received -->
72+
<div id="success-message" class="success hidden">
73+
<strong>Success!</strong> Received the manifest code from GitHub:
74+
<div class="code-block" id="manifest-code"></div>
75+
<p>You can now use this code with the script:</p>
76+
<div class="code-block" id="script-command"></div>
77+
</div>
78+
79+
<form action="https://github.com/organizations/joshjohanning-org/settings/apps/new?state=abc123" method="post">
80+
<h3>App Manifest Configuration:</h3>
81+
<textarea name="manifest" id="manifest" readonly></textarea>
82+
<br><br>
83+
<button type="submit">Create GitHub App from Manifest</button>
84+
</form>
85+
86+
<details>
87+
<summary>📋 How to use the manifest code (click to expand)</summary>
88+
<div class="dropdown-content">
89+
<p>Once you get the code back from GitHub, you can use it with the conversion script:</p>
90+
<div class="code-block">./create-github-app-from-manifest.sh YOUR_CODE_HERE</div>
91+
<p>Or manually with gh CLI:</p>
92+
<div class="code-block">gh api \
93+
--method POST \
94+
-H "Accept: application/vnd.github+json" \
95+
-H "X-GitHub-Api-Version: 2022-11-28" \
96+
/app-manifests/YOUR_CODE_HERE/conversions</div>
97+
</div>
98+
</details>
99+
</div>
100+
101+
<script>
102+
// Determine the current URL - use localhost if we're on a local server
103+
let redirectUrl = 'http://localhost:8000/github-app-manifest-form.html';
104+
105+
// If we're already on a proper HTTP server, use the current location
106+
if (window.location.protocol === 'http:' || window.location.protocol === 'https:') {
107+
redirectUrl = window.location.href.split('?')[0]; // Remove any existing query params
108+
}
109+
110+
// Sample manifest configuration
111+
const manifest = {
112+
"name": "Test App from Manifest",
113+
"url": "https://www.example.com",
114+
"hook_attributes": {
115+
"url": "https://example.com/github/events"
116+
},
117+
"redirect_url": redirectUrl,
118+
"callback_urls": [
119+
redirectUrl
120+
],
121+
"public": false,
122+
"default_permissions": {
123+
"metadata": "read",
124+
"contents": "read",
125+
"issues": "write"
126+
},
127+
"default_events": [
128+
"issues",
129+
"issue_comment",
130+
"push"
131+
]
132+
};
133+
134+
// Populate the textarea with the manifest JSON
135+
document.getElementById('manifest').value = JSON.stringify(manifest, null, 2);
136+
137+
// Check if we're returning from GitHub with a code
138+
window.addEventListener('load', function() {
139+
const urlParams = new URLSearchParams(window.location.search);
140+
const code = urlParams.get('code');
141+
const state = urlParams.get('state');
142+
143+
if (code) {
144+
// Show success message
145+
document.getElementById('success-message').classList.remove('hidden');
146+
document.getElementById('manifest-code').textContent = code;
147+
document.getElementById('script-command').textContent = `./create-github-app-from-manifest.sh ${code}`;
148+
149+
// Scroll to success message
150+
document.getElementById('success-message').scrollIntoView({ behavior: 'smooth' });
151+
}
152+
});
153+
</script>
154+
</body>
155+
</html>

scripts/README.md

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,7 @@ Configuration values to change in the script:
3636

3737
Migrate work items from Azure DevOps to GitHub issues - this just links out to a [separate repo](https://github.com/joshjohanning/ado_workitems_to_github_issues)
3838

39-
## delete-branch-protection-rules.ps1
40-
41-
Delete branch protection rules programmatically based on a pattern.
42-
43-
## gei-clean-up-azure-storage-account.sh
44-
45-
Clean up Azure Storage Account Containers from GEI migrations.
46-
47-
## get-app-jwt.py
39+
## create-app-jwt.py
4840

4941
This script will generate a JWT for a GitHub App. It will use the private key and app ID from the GitHub App's settings page.
5042

@@ -55,6 +47,29 @@ This script will generate a JWT for a GitHub App. It will use the private key an
5547

5648
Script sourced from [GitHub docs](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app#example-using-python-to-generate-a-jwt).
5749

50+
## create-app-jwt.sh
51+
52+
Generate a JWT (JSON Web Token) for a GitHub App using bash. This is a shell script alternative to `create-app-jwt.py`.
53+
54+
Usage:
55+
56+
```bash
57+
./create-app-jwt.sh <client-id> <path-to-private-key.pem>
58+
```
59+
60+
The script generates a JWT that is valid for 10 minutes, which can be used to authenticate as a GitHub App and obtain installation tokens.
61+
62+
> [!NOTE]
63+
> Requires `openssl` to be installed. The JWT can be used with the GitHub API to generate installation access tokens.
64+
65+
## delete-branch-protection-rules.ps1
66+
67+
Delete branch protection rules programmatically based on a pattern.
68+
69+
## gei-clean-up-azure-storage-account.sh
70+
71+
Clean up Azure Storage Account Containers from GEI migrations.
72+
5873
## get-app-tokens-for-each-installation.sh
5974

6075
This script will generate generate a JWT for a GitHub app and use that JWT to generate installation tokens for each org installation. The installation tokens, returned as `ghs_abc`, can then be used for normal API calls. It will use the private key and app ID from the GitHub App's settings page and the `get-app-jwt.py` script to generate the JWT, and then use the JWT to generate the installation tokens for each org installation.
@@ -94,6 +109,26 @@ My use case is to use this list to determine who needs to be added to a organiza
94109
1. Run: `./new-users-to-add-to-project.sh <org> <repo> <file>`
95110
2. Don't delete the `<file>` as it functions as your user database
96111

112+
## github-app-manifest-scripts
113+
114+
Scripts to create GitHub Apps using the [GitHub App Manifest flow](https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest). The manifest flow allows you to create a GitHub App by posting a JSON manifest to GitHub, which then generates the app credentials.
115+
116+
**Workflow:**
117+
118+
1. **Generate HTML form**: Run `generate-github-app-manifest-form.sh [organization]` to create an HTML file
119+
2. **Start local server**: Serve the HTML file (e.g., `python3 -m http.server 8000`)
120+
3. **Create app via browser**: Open the form, submit it to GitHub, and get redirected back with a manifest code
121+
4. **Convert manifest code**: Use `create-github-app-from-manifest.sh` to convert the code into app credentials
122+
123+
**Scripts:**
124+
125+
- **generate-github-app-manifest-form.sh**: Generates an HTML form for the manifest flow
126+
- **create-github-app-from-manifest.sh**: Converts manifest code into app credentials with detailed output and error handling
127+
- **github-app-manifest-form-example.html**: Example HTML form (generated output)
128+
129+
> [!NOTE]
130+
> Requires `curl`, `jq`, and a classic GitHub personal access token (`ghp_*`). Fine-grained tokens are not supported for the manifest conversion endpoint.
131+
97132
## migrate-discussions
98133

99134
See: [migrate-discussions](./migrate-discussions/README.md)
File renamed without changes.

scripts/create-app-jwt.sh

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env bash
2+
3+
set -o pipefail
4+
client_id=$1 # Client ID as first argument
5+
6+
pem=$( cat $2 ) # file path of the private key as second argument
7+
8+
now=$(date +%s)
9+
iat=$((${now} - 60)) # Issues 60 seconds in the past
10+
exp=$((${now} + 600)) # Expires 10 minutes in the future
11+
12+
b64enc() { openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n'; }
13+
14+
header_json='{
15+
"typ":"JWT",
16+
"alg":"RS256"
17+
}'
18+
# Header encode
19+
header=$( echo -n "${header_json}" | b64enc )
20+
21+
payload_json="{
22+
\"iat\":${iat},
23+
\"exp\":${exp},
24+
\"iss\":\"${client_id}\"
25+
}"
26+
# Payload encode
27+
payload=$( echo -n "${payload_json}" | b64enc )
28+
29+
# Signature
30+
header_payload="${header}"."${payload}"
31+
signature=$(
32+
openssl dgst -sha256 -sign <(echo -n "${pem}") \
33+
<(echo -n "${header_payload}") | b64enc
34+
)
35+
36+
# Create JWT
37+
JWT="${header_payload}"."${signature}"
38+
printf '%s\n' "$JWT"

0 commit comments

Comments
 (0)