Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: API for installation #9

Merged
merged 3 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions server/model/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ class BindUser(Base):
email = db.Column(db.String(128), nullable=True, comment="邮箱")
name = db.Column(db.String(128), nullable=True, comment="用户名")
avatar = db.Column(db.String(128), nullable=True, comment="头像")
access_token = db.Column(
db.String(128), nullable=True, comment="GitHub access_token"
)
extra = db.Column(
JSONStr(1024), nullable=True, server_default=text("'{}'"), comment="用户其他字段"
)
Expand Down
13 changes: 8 additions & 5 deletions server/routes/github.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import logging
import os

from app import app
from flask import Blueprint, abort, redirect, request, session
from model.schema import Team
from utils.auth import authenticated
from utils.github.common import get_installation_token, get_jwt, verify_github_signature
from utils.github.application import (
get_installation_token,
get_jwt,
verify_github_signature,
)
from utils.user import register

bp = Blueprint("github", __name__, url_prefix="/api/github")
Expand All @@ -25,7 +28,7 @@ def github_install():
)


@bp.route("/register", methods=["GET"])
@bp.route("/oauth", methods=["GET"])
def github_register():
"""GitHub OAuth register.

Expand Down Expand Up @@ -57,9 +60,9 @@ def github_hook():

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x_github_event = request.headers.get("x-github-event", None)

logging.info(x_github_event)
app.logger.info(x_github_event)

logging.debug(request.json)
app.logger.debug(request.json)

return "Receive Success!"

Expand Down
69 changes: 69 additions & 0 deletions server/utils/github/account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import httpx
from app import app


def get_user_info(access_token: str) -> dict | None:
"""Get user info by access token.

Args:
access_token (str): The user access token.

Returns:
dict: User info.
"""

with httpx.Client() as client:
response = client.get(
"https://api.github.com/user",
headers={
"Accept": "application/vnd.github.v3+json",
"Authorization": f"token {access_token}",
},
)
if response.status_code != 200:
app.logger.debug(f"Failed to get user info. {response.text}")
return None

user_info = response.json()
return user_info

app.logger.debug("Failed to get user info.")
return None


def get_email(access_token: str) -> str | None:
"""Get user email by access token.

Args:
access_token (str): The user access token.

Returns:
str: User email.
"""

with httpx.Client() as client:
response = client.get(
"https://api.github.com/user/emails",
headers={
"Accept": "application/vnd.github.v3+json",
"Authorization": f"Bearer {access_token}",
"X-GitHub-Api-Version": "2022-11-28",
},
)
if response.status_code != 200:
app.logger.debug(f"Failed to get user email. {response.text}")
return None

user_emails = response.json()
if len(user_emails) == 0:
app.logger.debug("Failed to get user email.")
return None

for user_email in user_emails:
if user_email["primary"]:
return user_email["email"]

return user_emails[0]["email"]

app.logger.debug("Failed to get user email.")
return None
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import hashlib
import hmac
import logging
import os
import time
from functools import wraps
from urllib.parse import parse_qs

import httpx
from app import app
from flask import abort, request
from jwt import JWT, jwk_from_pem

Expand Down Expand Up @@ -63,7 +63,7 @@ def get_installation_token(jwt: str, installation_id: str) -> str | None:
},
)
if response.status_code != 200:
logging.debug(f"Failed to get installation token. {response.text}")
app.logger.debug(f"Failed to get installation token. {response.text}")
return None

installation_token = response.json().get("token", None)
Expand Down Expand Up @@ -92,12 +92,13 @@ def oauth_by_code(code: str) -> dict | None:
},
)
if response.status_code != 200:
app.logger.debug(f"Failed to get access token. {response.text}")
return None

try:
oauth_info = parse_qs(response.text)
except Exception as e:
logging.debug(e)
app.logger.debug(e)
return None

return oauth_info
Expand Down Expand Up @@ -132,42 +133,14 @@ def wrapper(*args, **kwargs):
)
expected_signature = "sha256=" + hash_object.hexdigest()

logging.debug(f"{expected_signature} {signature}")
app.logger.debug(f"{expected_signature} {signature}")

if not hmac.compare_digest(expected_signature, signature):
logging.debug("Invalid signature.")
app.logger.debug("Invalid signature.")
abort(403, "Invalid signature.")

return func(*args, **kwargs)

return wrapper

return decorator


def get_user_info(access_token: str):
"""Get user info by access token.

Args:
access_token (str): The user access token.

Returns:
dict: User info.
"""

with httpx.Client() as client:
response = client.get(
"https://api.github.com/user",
headers={
"Accept": "application/vnd.github.v3+json",
"Authorization": f"token {access_token}",
},
)
if response.status_code != 200:
logging.debug(f"Failed to get user info. {response.text}")
return None

user_info = response.json()
return user_info

return None
16 changes: 11 additions & 5 deletions server/utils/user.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from app import app, db
from flask import abort
from model.schema import BindUser, ObjID, User
from utils.github.common import get_user_info, oauth_by_code
from utils.github.account import get_email, get_user_info
from utils.github.application import oauth_by_code


def register(code: str) -> str | None:
Expand All @@ -10,6 +12,8 @@ def register(code: str) -> str | None:
"""

oauth_info = oauth_by_code(code) # 获取 access token
if oauth_info is None:
abort(500)

access_token = oauth_info.get("access_token", None)[0] # 这里要考虑取哪个,为什么会有多个?

Expand All @@ -23,12 +27,12 @@ def register(code: str) -> str | None:
if user is not None:
return user.id

email = get_email(access_token)

new_user = User(
id=ObjID.new_id(),
github_id=github_id,
email=user_info.get(
"email", None
), # 这里的邮箱其实是公开邮箱,可能会获取不到 TODO: 换成使用用户邮箱 API 来获取
email=email, # 这里的邮箱其实是公开邮箱,可能会获取不到 TODO: 换成使用用户邮箱 API 来获取
name=user_info.get("login", None),
avatar=user_info.get("avatar_url", None),
extra=user_info,
Expand All @@ -41,8 +45,10 @@ def register(code: str) -> str | None:
id=ObjID.new_id(),
user_id=new_user.id,
platform="github",
email=user_info.get("email", None),
email=email,
name=user_info.get("login", None),
avatar=user_info.get("avatar_url", None),
access_token=access_token,
jingfelix marked this conversation as resolved.
Show resolved Hide resolved
extra=oauth_info,
)

Expand Down