diff --git a/onboard_live_backend/main.py b/onboard_live_backend/main.py
index 1aa2f52..3469d79 100644
--- a/onboard_live_backend/main.py
+++ b/onboard_live_backend/main.py
@@ -7,6 +7,7 @@ from random import choice
from secrets import token_hex
from typing import Dict, List
+from fastapi.responses import HTMLResponse, RedirectResponse
import httpx
import uvicorn
from apscheduler.schedulers.asyncio import AsyncIOScheduler
@@ -17,6 +18,7 @@ from fastapi.middleware.cors import CORSMiddleware
from prisma import Prisma
from slack_bolt.adapter.fastapi.async_handler import AsyncSlackRequestHandler
from slack_bolt.async_app import AsyncAck, AsyncApp
+from cryptography.fernet import Fernet
load_dotenv(dotenv_path="./.env")
@@ -25,6 +27,8 @@ active_streams: List[Dict[str, str | bool]] = []
scheduler = AsyncIOScheduler()
+FERNET = Fernet(os.environ["FERNET_KEY"])
+
def verify_gh_signature(payload_body, secret_token, signature_header):
"""Verify that the payload was sent from GitHub by validating SHA256.
@@ -155,6 +159,41 @@ bolt = AsyncApp(
bolt_handler = AsyncSlackRequestHandler(bolt)
+@api.get("/auth/github/login")
+async def github_redirect(request: Request):
+ return RedirectResponse(
+ f"https://github.com/login/oauth/authorize?client_id={os.environ['GH_CLIENT_ID']}&redirect_uri=https://live.onboard.hackclub.com/auth/github/callback&scopes=read:user&state={request.query_params["state"]}"
+ )
+
+
+@api.get("/auth/github/callback")
+async def github_callback(request: Request):
+ code: str = request.query_params["code"]
+ state: str = request.query_params["state"]
+ user_id, pr_id = FERNET.decrypt(bytes.fromhex(state)).decode().split("+")
+ db_user = await db.user.find_first_or_raise(where={"slack_id": user_id})
+ db_pr = await db.pullrequest.find_first_or_raise(where={"github_id": int(pr_id)})
+ async with httpx.AsyncClient() as client:
+ token = (
+ await client.post(
+ "https://github.com/login/oauth/access_token",
+ json={
+ "client_id": os.environ["GH_CLIENT_ID"],
+ "client_secret": os.environ["GH_CLIENT_SECRET"],
+ "code": code,
+ "redirect_uri": "https://live.onboard.hackclub.com/auth/github/callback",
+ },
+ headers={"Accept": "application/json"},
+ )
+ ).json()["access_token"]
+
+ gh_user = (await client.get("https://api.github.com/user", headers={"Accept": "application/vnd.github.v3+json", "Authorization": f"Bearer {token}"})).json()["id"]
+ if gh_user == db_pr.gh_user_id:
+ await db.pullrequest.update({"user": {"connect": {"id": db_user.id}}, "gh_user_id": gh_user}, {"id": db_pr.id})
+ return HTMLResponse("
Success! Your PR has been linked to your account. Check your Slack DMs for the next steps!
")
+ return HTMLResponse("Looks like something went wrong! DM @mra on slack.
", status_code=403)
+
+
@api.post("/api/v1/github/pr_event")
async def pr_event(request: Request):
verify_gh_signature(
@@ -166,31 +205,7 @@ async def pr_event(request: Request):
if body["action"] == "labeled":
if body["label"]["id"] == 7336079497:
print("Added label has same id as OBL label!")
- async with httpx.AsyncClient() as client:
- db_pr = await db.pullrequest.create({"github_id": body["number"]})
- db_pr_token = db_pr.token
- await client.post(
- f"https://api.github.com/repos/hackclub/OnBoard/issues/{body["issue"]["number"]}/comments",
- headers={
- "Authorization": f"token {os.environ['GH_TOKEN']}",
- "Accept": "application/vnd.github.v3+json",
- },
- json={
- "body": f"Hey, I'm Micha, a.k.a `@mra` on Slack! It looks like this is an OnBoard Live submission. If that sounds right, then go to the #onboard-live channel on Slack and send the message `/onboard-live-submit {db_pr_token}`. Doing that helps us link this pull request to your Slack account lets you select your sessions for review.\n###### If you have no clue what OnBoard Live is, please disregard this automated message!"
- },
- )
- elif "created" in body and "comment" in body:
- if body["comment"]["user"]["id"] == body["issue"]["user"]["id"]:
- db_pr = await db.pullrequest.find_first(where={"github_id": body["issue"]["number"]})
- if db_pr:
- if db_pr.possible_users:
- for user in db_pr.possible_users:
- if hashlib.sha256(bytes(f"{db_pr.secondary_token}+{user.slack_id}", encoding="utf-8")).hexdigest() in body["comment"]["body"]:
- # Yay, the user who ran the Slack submit command is the same user who submitted the PR!
- db_pr.user = user
- break
- else:
- print("possible users was none")
+ await db.pullrequest.create({"github_id": body["pull_request"]["number"], "gh_user_id": body["pull_request"]["user"]["id"]})
return
@@ -409,20 +424,18 @@ async def submit(ack: AsyncAck, command):
user_id = command["user_id"]
channel_id = command["channel_id"]
text = command["text"]
- db_pr = await db.pullrequest.find_first(where={"token": text})
- db_user = await db.user.find_first_or_raise(where={"slack_id": user_id})
+ db_pr = await db.pullrequest.find_first(where={"github_id": int(text)})
if db_pr is None:
await bolt.client.chat_postEphemeral(
channel=channel_id,
user=user_id,
- text="There doesn't seem to be a PR open with that token! If this seems like a mistake, please message <@U05C64XMMHV> about it!",
+ text="There doesn't seem to be a PR open with that ID! If this seems like a mistake, please message <@U05C64XMMHV> about it!",
)
return
- await db.pullrequest.update(where={"id": db_pr.id}, data={"possible_users": {"set": [{"id": db_user.id}]}})
await bolt.client.chat_postEphemeral(
channel=channel_id,
user=user_id,
- text=f"Please go to and add a comment containing the secret code `{hashlib.sha256(bytes(f"{db_pr.secondary_token}+{user_id}", encoding="utf-8")).hexdigest()}`. This helps us make sure this is your PR!",
+ text=f"Please to authenticate with GitHub. This helps us verify that this is your PR!",
)
diff --git a/onboard_live_backend/migrations/20240821135629_prep_for_oauth/migration.sql b/onboard_live_backend/migrations/20240821135629_prep_for_oauth/migration.sql
new file mode 100644
index 0000000..4b1ef4e
--- /dev/null
+++ b/onboard_live_backend/migrations/20240821135629_prep_for_oauth/migration.sql
@@ -0,0 +1,10 @@
+/*
+ Warnings:
+
+ - You are about to drop the `_PullRequestToPossibleUser` table. If the table is not empty, all the data it contains will be lost.
+
+*/
+-- DropTable
+PRAGMA foreign_keys=off;
+DROP TABLE "_PullRequestToPossibleUser";
+PRAGMA foreign_keys=on;
diff --git a/onboard_live_backend/migrations/20240823141450_refactor_schema_for_oauth/migration.sql b/onboard_live_backend/migrations/20240823141450_refactor_schema_for_oauth/migration.sql
new file mode 100644
index 0000000..09deab9
--- /dev/null
+++ b/onboard_live_backend/migrations/20240823141450_refactor_schema_for_oauth/migration.sql
@@ -0,0 +1,26 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `known_user_id` on the `PullRequest` table. All the data in the column will be lost.
+ - You are about to drop the column `secondary_token` on the `PullRequest` table. All the data in the column will be lost.
+ - You are about to drop the column `token` on the `PullRequest` table. All the data in the column will be lost.
+
+*/
+-- AlterTable
+ALTER TABLE "User" ADD COLUMN "github_user_id" TEXT;
+
+-- RedefineTables
+PRAGMA defer_foreign_keys=ON;
+PRAGMA foreign_keys=OFF;
+CREATE TABLE "new_PullRequest" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "github_id" INTEGER NOT NULL,
+ "user_id" TEXT,
+ CONSTRAINT "PullRequest_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
+);
+INSERT INTO "new_PullRequest" ("github_id", "id") SELECT "github_id", "id" FROM "PullRequest";
+DROP TABLE "PullRequest";
+ALTER TABLE "new_PullRequest" RENAME TO "PullRequest";
+CREATE UNIQUE INDEX "PullRequest_github_id_key" ON "PullRequest"("github_id");
+PRAGMA foreign_keys=ON;
+PRAGMA defer_foreign_keys=OFF;
diff --git a/onboard_live_backend/migrations/20240823141852_add_gh_token_field/migration.sql b/onboard_live_backend/migrations/20240823141852_add_gh_token_field/migration.sql
new file mode 100644
index 0000000..994913c
--- /dev/null
+++ b/onboard_live_backend/migrations/20240823141852_add_gh_token_field/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "User" ADD COLUMN "github_token" TEXT;
diff --git a/onboard_live_backend/migrations/20240823145449_more_pr_schema_work/migration.sql b/onboard_live_backend/migrations/20240823145449_more_pr_schema_work/migration.sql
new file mode 100644
index 0000000..cce7526
--- /dev/null
+++ b/onboard_live_backend/migrations/20240823145449_more_pr_schema_work/migration.sql
@@ -0,0 +1,25 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `github_token` on the `User` table. All the data in the column will be lost.
+
+*/
+-- AlterTable
+ALTER TABLE "PullRequest" ADD COLUMN "gh_user_id" TEXT;
+
+-- RedefineTables
+PRAGMA defer_foreign_keys=ON;
+PRAGMA foreign_keys=OFF;
+CREATE TABLE "new_User" (
+ "id" TEXT NOT NULL PRIMARY KEY,
+ "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "slack_id" TEXT NOT NULL,
+ "name" TEXT NOT NULL,
+ "github_user_id" TEXT
+);
+INSERT INTO "new_User" ("created_at", "github_user_id", "id", "name", "slack_id") SELECT "created_at", "github_user_id", "id", "name", "slack_id" FROM "User";
+DROP TABLE "User";
+ALTER TABLE "new_User" RENAME TO "User";
+CREATE UNIQUE INDEX "User_slack_id_key" ON "User"("slack_id");
+PRAGMA foreign_keys=ON;
+PRAGMA defer_foreign_keys=OFF;
diff --git a/onboard_live_backend/migrations/20240823151408_remove_duplicated_field/migration.sql b/onboard_live_backend/migrations/20240823151408_remove_duplicated_field/migration.sql
new file mode 100644
index 0000000..815c31e
--- /dev/null
+++ b/onboard_live_backend/migrations/20240823151408_remove_duplicated_field/migration.sql
@@ -0,0 +1,21 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `github_user_id` on the `User` table. All the data in the column will be lost.
+
+*/
+-- RedefineTables
+PRAGMA defer_foreign_keys=ON;
+PRAGMA foreign_keys=OFF;
+CREATE TABLE "new_User" (
+ "id" TEXT NOT NULL PRIMARY KEY,
+ "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "slack_id" TEXT NOT NULL,
+ "name" TEXT NOT NULL
+);
+INSERT INTO "new_User" ("created_at", "id", "name", "slack_id") SELECT "created_at", "id", "name", "slack_id" FROM "User";
+DROP TABLE "User";
+ALTER TABLE "new_User" RENAME TO "User";
+CREATE UNIQUE INDEX "User_slack_id_key" ON "User"("slack_id");
+PRAGMA foreign_keys=ON;
+PRAGMA defer_foreign_keys=OFF;
diff --git a/onboard_live_backend/migrations/20240823151722_make_gh_user_id_required/migration.sql b/onboard_live_backend/migrations/20240823151722_make_gh_user_id_required/migration.sql
new file mode 100644
index 0000000..57b2450
--- /dev/null
+++ b/onboard_live_backend/migrations/20240823151722_make_gh_user_id_required/migration.sql
@@ -0,0 +1,22 @@
+/*
+ Warnings:
+
+ - Made the column `gh_user_id` on table `PullRequest` required. This step will fail if there are existing NULL values in that column.
+
+*/
+-- RedefineTables
+PRAGMA defer_foreign_keys=ON;
+PRAGMA foreign_keys=OFF;
+CREATE TABLE "new_PullRequest" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "github_id" INTEGER NOT NULL,
+ "user_id" TEXT,
+ "gh_user_id" TEXT NOT NULL,
+ CONSTRAINT "PullRequest_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
+);
+INSERT INTO "new_PullRequest" ("gh_user_id", "github_id", "id", "user_id") SELECT "gh_user_id", "github_id", "id", "user_id" FROM "PullRequest";
+DROP TABLE "PullRequest";
+ALTER TABLE "new_PullRequest" RENAME TO "PullRequest";
+CREATE UNIQUE INDEX "PullRequest_github_id_key" ON "PullRequest"("github_id");
+PRAGMA foreign_keys=ON;
+PRAGMA defer_foreign_keys=OFF;
diff --git a/onboard_live_backend/migrations/20240823152458_make_gh_user_id_an_int/migration.sql b/onboard_live_backend/migrations/20240823152458_make_gh_user_id_an_int/migration.sql
new file mode 100644
index 0000000..ccfb293
--- /dev/null
+++ b/onboard_live_backend/migrations/20240823152458_make_gh_user_id_an_int/migration.sql
@@ -0,0 +1,22 @@
+/*
+ Warnings:
+
+ - You are about to alter the column `gh_user_id` on the `PullRequest` table. The data in that column could be lost. The data in that column will be cast from `String` to `Int`.
+
+*/
+-- RedefineTables
+PRAGMA defer_foreign_keys=ON;
+PRAGMA foreign_keys=OFF;
+CREATE TABLE "new_PullRequest" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "github_id" INTEGER NOT NULL,
+ "user_id" TEXT,
+ "gh_user_id" INTEGER NOT NULL,
+ CONSTRAINT "PullRequest_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
+);
+INSERT INTO "new_PullRequest" ("gh_user_id", "github_id", "id", "user_id") SELECT "gh_user_id", "github_id", "id", "user_id" FROM "PullRequest";
+DROP TABLE "PullRequest";
+ALTER TABLE "new_PullRequest" RENAME TO "PullRequest";
+CREATE UNIQUE INDEX "PullRequest_github_id_key" ON "PullRequest"("github_id");
+PRAGMA foreign_keys=ON;
+PRAGMA defer_foreign_keys=OFF;
diff --git a/onboard_live_backend/schema.prisma b/onboard_live_backend/schema.prisma
index f10598b..59ff94a 100644
--- a/onboard_live_backend/schema.prisma
+++ b/onboard_live_backend/schema.prisma
@@ -1,7 +1,7 @@
generator client {
provider = "prisma-client-py"
- interface = "asyncio"
recursive_type_depth = "5"
+ interface = "asyncio"
}
datasource db {
@@ -10,13 +10,12 @@ datasource db {
}
model User {
- id String @id @default(cuid())
- created_at DateTime @default(now())
- slack_id String @unique
- name String
- stream Stream?
- possible_pulls PullRequest[] @relation(name: "PullRequestToPossibleUser") // pull requests that this user has tried to claim via the /onboard-live-submit command on Slack
- known_pulls PullRequest[] @relation(name: "PullRequestToKnownUser") // pull requests that have been verified to belong to this user
+ id String @id @default(cuid())
+ created_at DateTime @default(now())
+ slack_id String @unique
+ name String
+ pull_requests PullRequest[] @relation("PullRequestToUser")
+ stream Stream?
}
model Stream {
@@ -30,11 +29,9 @@ model Stream {
}
model PullRequest {
- id Int @id @default(autoincrement())
- github_id Int @unique
- user User? @relation(name: "PullRequestToKnownUser", fields: [known_user_id], references: [id])
- known_user_id String?
- token String @unique @default(uuid())
- secondary_token String @unique @default(uuid())
- possible_users User[] @relation(name: "PullRequestToPossibleUser")
+ id Int @id @default(autoincrement())
+ github_id Int @unique
+ user_id String?
+ gh_user_id Int
+ user User? @relation("PullRequestToUser", fields: [user_id], references: [id])
}