Compare commits

..

14 Commits

Author SHA1 Message Date
c3141df775 Check json 2022-02-23 10:25:19 +00:00
0217d98d4f Remove ref check 2022-02-23 10:24:26 +00:00
62be2d30f3 print json 2022-02-21 02:25:35 +00:00
0c113739ee Remove json request 2022-02-21 02:25:20 +00:00
a04f24fcff Change is to == 2022-02-20 05:53:10 +00:00
8c0872ea1a Remove ref check 2022-02-20 05:52:15 +00:00
de0a1809a4 Update readme 2021-07-28 21:55:02 +12:00
28b09e374a Remove Caddy. Add port 2021-07-28 21:53:26 +12:00
c3d363e15a Update readme 2021-07-28 21:52:40 +12:00
346c57d2b9 Update 2021-07-28 21:52:26 +12:00
2451d5b000 Update 2021-07-28 21:51:58 +12:00
3ff1800869 Restructure 2021-07-28 21:51:48 +12:00
683a4b487e Remove dotenv Move test files. Get run dev working 2021-07-28 21:51:05 +12:00
b8267c2040 Switch to Fastapi 2021-07-28 21:48:30 +12:00
12 changed files with 60 additions and 79 deletions

View File

@@ -1,2 +1,3 @@
DOMAIN = BRANCH=
EMAIL = TOKEN=
WEBHOOK_SECRET=

View File

@@ -1,11 +1,11 @@
FROM python:slim FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
ENV DOCKER=1 ENV DOCKER=1
RUN pip install aiohttp RUN pip install fastapi-responses
COPY src /src COPY ./app /app/app
CMD [ "python", "/src/main.py"]

View File

@@ -11,11 +11,10 @@ requests = "*"
fastapi = "*" fastapi = "*"
uvicorn = {extras = ["standard"], version = "*"} uvicorn = {extras = ["standard"], version = "*"}
fastapi-responses = "*" fastapi-responses = "*"
python-dotenv = "*"
[requires] [requires]
python_version = "3.8" python_version = "3.8"
[scripts] [scripts]
test = "pytest src/test.py -s --capture=sys" test = "pytest app/test/test.py -s --capture=sys"
dev = "uvicorn src/main:app --reload" dev = "uvicorn app.main:app --reload"

3
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "3316652f79feb318e6104f936f3380ec7feee77cab7033b07666e40461611dc6" "sha256": "4ce5e23dc521f0bcb803b2fa8327b19f956a56bc4267a13b1a0dd644af024078"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -109,7 +109,6 @@
"sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1", "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1",
"sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172" "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"
], ],
"index": "pypi",
"version": "==0.19.0" "version": "==0.19.0"
}, },
"pyyaml": { "pyyaml": {

View File

@@ -1,2 +1,15 @@
# webhook # Github Web Hook
Example of how to use Github web hooks using Fastapi
```pipenv sync```
```pipenv run test```
```cp .env.sample .env```
```pipenv run dev```
```docker-compose up --build```

0
app/__init__.py Normal file
View File

View File

@@ -1,26 +1,17 @@
import os from os import getenv
import hmac import hmac
from fastapi import Request from fastapi import Request
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from fastapi.param_functions import Header
from dotenv import load_dotenv
load_dotenv()
async def check_ref(request: Request):
json = await request.json()
if json["ref"] and json["ref"] == f"refs/heads/{os.environ.get('BRANCH')}":
return
raise HTTPException(status_code=403, detail="Invalid branch")
async def auth_hook(request: Request): async def auth_hook(request: Request):
try: try:
json = await request.json()
text = await request.body() text = await request.body()
json = await request.json()
except: except:
raise HTTPException(status_code=204, detail="Missing or bad content") raise HTTPException(status_code=204, detail="Missing or bad content")
header_signature = request.headers.get('X-Hub-Signature')
header_signature = request.headers.get('X-Hub-Signature')
if not header_signature: if not header_signature:
raise HTTPException(status_code=400, detail="Missing signature") raise HTTPException(status_code=400, detail="Missing signature")
@@ -29,7 +20,7 @@ async def auth_hook(request: Request):
if sha_name != 'sha1': if sha_name != 'sha1':
raise HTTPException(status_code=400, detail="Invalid signature") raise HTTPException(status_code=400, detail="Invalid signature")
secret_key = os.environ.get('WEBHOOK_SECRET') secret_key = getenv('WEBHOOK_SECRET')
if secret_key is None: if secret_key is None:
raise HTTPException(status_code=503, detail="Missing WEBHOOK_SECRET") raise HTTPException(status_code=503, detail="Missing WEBHOOK_SECRET")
@@ -37,14 +28,14 @@ async def auth_hook(request: Request):
mac = hmac.new(secret_key.encode(), msg=text, digestmod='sha1') mac = hmac.new(secret_key.encode(), msg=text, digestmod='sha1')
# verify the digest matches the signature # verify the digest matches the signature
print(f'{mac.hexdigest()} {signature}')
if not hmac.compare_digest(mac.hexdigest(), signature): if not hmac.compare_digest(mac.hexdigest(), signature):
raise HTTPException(status_code=403, detail="Unauthorized") raise HTTPException(status_code=403, detail="Unauthorized")
async def auth_web(request: Request): async def auth_web(request: Request):
token = request._query_params.get("token") token = request._query_params.get("token")
if token is None: if token == None or token == "":
raise HTTPException(status_code=400, detail="Missing token") raise HTTPException(status_code=400, detail="Missing token")
print(token, os.environ.get("TOKEN")) if token == getenv("TOKEN"):
if token == os.environ.get("TOKEN"):
return return
raise HTTPException(status_code=403, detail="Invalid token") raise HTTPException(status_code=403, detail="Invalid token")

16
app/main.py Normal file
View File

@@ -0,0 +1,16 @@
from fastapi import FastAPI, Request, Depends
from fastapi_responses import custom_openapi
from app.dependencies import auth_hook, auth_web
app = FastAPI()
app.openapi = custom_openapi(app)
@app.get("/", dependencies=[Depends(auth_web)])
@app.post("/", dependencies=[Depends(auth_hook)])
async def hook(req: Request):
json = await req.json()
print(json)
return "Update"

3
app/test/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

View File

@@ -2,17 +2,15 @@ from fastapi import FastAPI, Request, Depends
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
import hmac import hmac
from app.main import app
from starlette.routing import request_response from app.dependencies import auth_hook, auth_web
from main import app from os import environ, getenv
from auth import auth_hook, auth_web, check_ref
from os import environ
import json import json
environ['WEBHOOK_SECRET'] = "dfsgdsjghhgdaehlsdfjhjkdh" environ['WEBHOOK_SECRET'] = "dfsgdsjghhgdaehlsdfjhjkdh"
environ["BRANCH"] = "master" environ["BRANCH"] = "master"
environ["TOKEN"] = "assdcvfgvh" environ["TOKEN"] = "assdcvfgvh"
secret_key = environ.get('WEBHOOK_SECRET') secret_key = getenv('WEBHOOK_SECRET')
client = TestClient(app) client = TestClient(app)
@@ -20,10 +18,6 @@ client = TestClient(app)
async def auth_test_handler(request: Request): async def auth_test_handler(request: Request):
return 200 return 200
@app.post("/test_ref", dependencies=[Depends(check_ref)])
async def auth_test_handler(request: Request):
return 200
@app.get("/test_web", dependencies=[Depends(auth_web)]) @app.get("/test_web", dependencies=[Depends(auth_web)])
async def web_test_hnadler(request: Request): async def web_test_hnadler(request: Request):
return 200 return 200
@@ -52,18 +46,8 @@ def test_auth():
assert response.status_code == 403 assert response.status_code == 403
assert response.text == '{"detail":"Unauthorized"}' assert response.text == '{"detail":"Unauthorized"}'
def test_branch():
payload = {"ref": "refs/heads/master"}
response = client.post("/test_ref", json= payload)
assert response.status_code == 200
payload = {"ref": "refs/heads/test"}
response = client.post("/test_ref", json= payload)
assert response.status_code == 403
def test_web(): def test_web():
response = client.get('/test_web?token={}'.format(environ.get("TOKEN"))) response = client.get('/test_web?token={}'.format(getenv("TOKEN")))
assert response.status_code == 200 assert response.status_code == 200
response = client.get('/test_web') response = client.get('/test_web')

View File

@@ -8,15 +8,15 @@ services:
networks: networks:
- caddy - caddy
restart: unless-stopped restart: unless-stopped
labels:
caddy: ${DOMAIN}
caddy.tls: ${EMAIL}
caddy.reverse_proxy: "{{upstreams 8080}}"
logging: logging:
driver: "json-file" driver: "json-file"
options: options:
max-size: "1m" max-size: "1m"
tty: true tty: true
env_file:
.env
ports:
- 8000:80
networks: networks:
caddy: caddy:

View File

@@ -1,25 +0,0 @@
from os import environ
import os
from fastapi import FastAPI, Body, Request, Depends
import json
from fastapi.exceptions import HTTPException
from fastapi.param_functions import Header
from fastapi_responses import custom_openapi
from auth import auth_hook, auth_web, check_ref
if not os.environ.get("DOCKER"):
from dotenv import load_dotenv
load_dotenv
app = FastAPI()
app.openapi = custom_openapi(app)
@app.get("/", dependencies=[Depends(auth_web)])
@app.post("/", dependencies=[Depends(auth_hook), Depends(check_ref)])
async def hook(req: Request):
return "Update"