-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support GitHub OAuth and App install
Signed-off-by: jingfelix <[email protected]>
- Loading branch information
Showing
7 changed files
with
250 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
from .github import * | ||
from .lark import * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import os | ||
|
||
from app import app | ||
from flask import Blueprint, abort, redirect, request | ||
from utils.github import get_installation_token, get_jwt, register_by_code | ||
|
||
bp = Blueprint("github", __name__, url_prefix="/api/github") | ||
|
||
|
||
@bp.route("/install", methods=["GET"]) | ||
def github_install(): | ||
installation_id = request.args.get("installation_id", None) | ||
|
||
if installation_id is None: | ||
return redirect( | ||
f"https://github.com/apps/{os.environ.get('GITHUB_APP_NAME')}/installations/new" | ||
) | ||
|
||
print(f"installation_id: {installation_id}") | ||
|
||
jwt = get_jwt( | ||
os.environ.get("GITHUB_APP_PRIVATE_KEY_PATH"), | ||
os.environ.get("GITHUB_APP_ID"), | ||
) | ||
|
||
installation_token = get_installation_token(jwt, installation_id) | ||
if installation_token is None: | ||
print("Failed to get installation token.") | ||
|
||
# TODO: 统一解决各类 http 请求失败的情况 | ||
abort(500) | ||
print(f"installation_token: {installation_token}") | ||
|
||
# 如果有 code 参数,则为该用户注册 | ||
code = request.args.get("code", None) | ||
if code is not None: | ||
print(f"code: {code}") | ||
|
||
user_token = register_by_code(code) | ||
if user_token is None: | ||
print("Failed to register by code.") | ||
abort(500) | ||
|
||
print(f"user_token: {user_token}") | ||
|
||
return "Success!" | ||
|
||
|
||
app.register_blueprint(bp) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import os | ||
import time | ||
|
||
import httpx | ||
from jwt import JWT, jwk_from_pem | ||
|
||
|
||
def get_jwt(pem_path: str, app_id: str) -> str: | ||
"""Generate a JSON Web Token (JWT) for authentication. | ||
Args: | ||
pem_path (str): path to the private key file. | ||
app_id (str): GitHub App's identifier. | ||
Returns: | ||
str: JWT. | ||
""" | ||
|
||
# Open PEM | ||
with open(pem_path, "rb") as pem_file: | ||
signing_key = jwk_from_pem(pem_file.read()) | ||
|
||
payload = { | ||
# Issued at time | ||
"iat": int(time.time()), | ||
# JWT expiration time (10 minutes maximum) | ||
"exp": int(time.time()) + 600, | ||
# GitHub App's identifier | ||
"iss": app_id, | ||
} | ||
|
||
# Create JWT | ||
jwt_instance = JWT() | ||
encoded_jwt = jwt_instance.encode(payload, signing_key, alg="RS256") | ||
|
||
return encoded_jwt | ||
|
||
|
||
def get_installation_token(jwt: str, installation_id: str) -> str | None: | ||
"""Get installation access token | ||
Args: | ||
jwt (str): The JSON Web Token used for authentication. | ||
installation_id (str): The ID of the installation. | ||
Returns: | ||
str: The installation access token. | ||
""" | ||
|
||
with httpx.Client() as client: | ||
response = client.post( | ||
f"https://api.github.com/app/installations/{installation_id}/access_tokens", | ||
headers={ | ||
"Accept": "application/vnd.github+json", | ||
"Authorization": f"Bearer {jwt}", | ||
"X-GitHub-Api-Version": "2022-11-28", | ||
}, | ||
) | ||
if response.status_code == 200: | ||
return None | ||
|
||
installation_token = response.json().get("token", None) | ||
return installation_token | ||
|
||
return None | ||
|
||
|
||
def register_by_code(code: str) -> str | None: | ||
"""Register by code | ||
Args: | ||
code (str): The code returned by GitHub OAuth. | ||
Returns: | ||
str: The user access token. | ||
""" | ||
|
||
with httpx.Client() as client: | ||
response = client.post( | ||
"https://github.com/login/oauth/access_token", | ||
params={ | ||
"client_id": os.environ.get("CLIENT_ID"), | ||
"client_secret": os.environ.get("CLIENT_SECRET"), | ||
"code": code, | ||
}, | ||
) | ||
if response.status_code == 200: | ||
return None | ||
|
||
return response.text | ||
|
||
return None |