:3
+
+ :3
+
+ login debug
+
diff --git a/.gitignore b/.gitignore
index 8f322f0..519c9b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+
+database.db*
diff --git a/.vsls.json b/.vsls.json
new file mode 100644
index 0000000..e46502d
--- /dev/null
+++ b/.vsls.json
@@ -0,0 +1,3 @@
+{
+ "gitignore": "none"
+}
diff --git a/environment.d.ts b/environment.d.ts
new file mode 100644
index 0000000..1eb7543
--- /dev/null
+++ b/environment.d.ts
@@ -0,0 +1,12 @@
+declare global {
+ namespace NodeJS {
+ interface ProcessEnv {
+ DISCORD_CLIENT_ID: string;
+ DISCORD_CLIENT_SECRET: string;
+ DISCORD_ALLOWED_GUILDS: string;
+ BASE_DOMAIN: string;
+ }
+ }
+}
+
+export {};
diff --git a/package-lock.json b/package-lock.json
index a3b75b8..afa6e12 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "gluestick",
"version": "0.1.0",
"dependencies": {
+ "@prisma/client": "^4.13.0",
"@types/node": "18.16.0",
"@types/react": "18.0.38",
"@types/react-dom": "18.0.11",
@@ -16,7 +17,12 @@
"next": "13.3.1",
"react": "18.2.0",
"react-dom": "18.2.0",
- "typescript": "5.0.4"
+ "typescript": "5.0.4",
+ "uuid": "^9.0.0"
+ },
+ "devDependencies": {
+ "@types/uuid": "^9.0.1",
+ "prisma": "^4.13.0"
}
},
"node_modules/@babel/runtime": {
@@ -311,6 +317,38 @@
"url": "https://opencollective.com/unts"
}
},
+ "node_modules/@prisma/client": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.13.0.tgz",
+ "integrity": "sha512-YaiiICcRB2hatxsbnfB66uWXjcRw3jsZdlAVxmx0cFcTc/Ad/sKdHCcWSnqyDX47vAewkjRFwiLwrOUjswVvmA==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@prisma/engines-version": "4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a"
+ },
+ "engines": {
+ "node": ">=14.17"
+ },
+ "peerDependencies": {
+ "prisma": "*"
+ },
+ "peerDependenciesMeta": {
+ "prisma": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@prisma/engines": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.13.0.tgz",
+ "integrity": "sha512-HrniowHRZXHuGT9XRgoXEaP2gJLXM5RMoItaY2PkjvuZ+iHc0Zjbm/302MB8YsPdWozAPHHn+jpFEcEn71OgPw==",
+ "devOptional": true,
+ "hasInstallScript": true
+ },
+ "node_modules/@prisma/engines-version": {
+ "version": "4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a.tgz",
+ "integrity": "sha512-fsQlbkhPJf08JOzKoyoD9atdUijuGBekwoOPZC3YOygXEml1MTtgXVpnUNchQlRSY82OQ6pSGQ9PxUe4arcSLQ=="
+ },
"node_modules/@rushstack/eslint-patch": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
@@ -362,6 +400,12 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
},
+ "node_modules/@types/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==",
+ "dev": true
+ },
"node_modules/@typescript-eslint/parser": {
"version": "5.59.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.1.tgz",
@@ -2673,6 +2717,23 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prisma": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.13.0.tgz",
+ "integrity": "sha512-L9mqjnSmvWIRCYJ9mQkwCtj4+JDYYTdhoyo8hlsHNDXaZLh/b4hR0IoKIBbTKxZuyHQzLopb/+0Rvb69uGV7uA==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "@prisma/engines": "4.13.0"
+ },
+ "bin": {
+ "prisma": "build/index.js",
+ "prisma2": "build/index.js"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -3216,6 +3277,14 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/uuid": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+ "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -3474,6 +3543,25 @@
"tslib": "^2.4.0"
}
},
+ "@prisma/client": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.13.0.tgz",
+ "integrity": "sha512-YaiiICcRB2hatxsbnfB66uWXjcRw3jsZdlAVxmx0cFcTc/Ad/sKdHCcWSnqyDX47vAewkjRFwiLwrOUjswVvmA==",
+ "requires": {
+ "@prisma/engines-version": "4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a"
+ }
+ },
+ "@prisma/engines": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.13.0.tgz",
+ "integrity": "sha512-HrniowHRZXHuGT9XRgoXEaP2gJLXM5RMoItaY2PkjvuZ+iHc0Zjbm/302MB8YsPdWozAPHHn+jpFEcEn71OgPw==",
+ "devOptional": true
+ },
+ "@prisma/engines-version": {
+ "version": "4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a.tgz",
+ "integrity": "sha512-fsQlbkhPJf08JOzKoyoD9atdUijuGBekwoOPZC3YOygXEml1MTtgXVpnUNchQlRSY82OQ6pSGQ9PxUe4arcSLQ=="
+ },
"@rushstack/eslint-patch": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
@@ -3525,6 +3613,12 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
},
+ "@types/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==",
+ "dev": true
+ },
"@typescript-eslint/parser": {
"version": "5.59.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.1.tgz",
@@ -5128,6 +5222,15 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
},
+ "prisma": {
+ "version": "4.13.0",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.13.0.tgz",
+ "integrity": "sha512-L9mqjnSmvWIRCYJ9mQkwCtj4+JDYYTdhoyo8hlsHNDXaZLh/b4hR0IoKIBbTKxZuyHQzLopb/+0Rvb69uGV7uA==",
+ "devOptional": true,
+ "requires": {
+ "@prisma/engines": "4.13.0"
+ }
+ },
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -5491,6 +5594,11 @@
"punycode": "^2.1.0"
}
},
+ "uuid": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+ "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
+ },
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/package.json b/package.json
index 0a41d71..5840de6 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
+ "@prisma/client": "^4.13.0",
"@types/node": "18.16.0",
"@types/react": "18.0.38",
"@types/react-dom": "18.0.11",
@@ -17,6 +18,11 @@
"next": "13.3.1",
"react": "18.2.0",
"react-dom": "18.2.0",
- "typescript": "5.0.4"
+ "typescript": "5.0.4",
+ "uuid": "^9.0.0"
+ },
+ "devDependencies": {
+ "@types/uuid": "^9.0.1",
+ "prisma": "^4.13.0"
}
}
diff --git a/prisma/migrations/20230425043204_init/migration.sql b/prisma/migrations/20230425043204_init/migration.sql
new file mode 100644
index 0000000..1e4bb67
--- /dev/null
+++ b/prisma/migrations/20230425043204_init/migration.sql
@@ -0,0 +1,19 @@
+-- CreateTable
+CREATE TABLE "AuthTicket" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "username" TEXT,
+ "ticket" TEXT NOT NULL
+);
+
+-- CreateTable
+CREATE TABLE "DiscordAuth" (
+ "id" TEXT NOT NULL PRIMARY KEY,
+ "authTicketId" INTEGER NOT NULL,
+ "accessToken" TEXT NOT NULL,
+ "refreshToken" TEXT NOT NULL,
+ "refreshAt" DATETIME NOT NULL,
+ CONSTRAINT "DiscordAuth_authTicketId_fkey" FOREIGN KEY ("authTicketId") REFERENCES "AuthTicket" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "DiscordAuth_authTicketId_key" ON "DiscordAuth"("authTicketId");
diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml
new file mode 100644
index 0000000..e5e5c47
--- /dev/null
+++ b/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "sqlite"
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
new file mode 100644
index 0000000..5069163
--- /dev/null
+++ b/prisma/schema.prisma
@@ -0,0 +1,26 @@
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "sqlite"
+ url = "file:./database.db"
+}
+
+model AuthTicket {
+ id Int @id @default(autoincrement())
+ username String?
+ ticket String
+ discordAuth DiscordAuth?
+}
+
+model DiscordAuth {
+ id String @id
+
+ authTicket AuthTicket @relation(fields: [authTicketId], references: [id])
+ authTicketId Int @unique
+
+ accessToken String
+ refreshToken String
+ refreshAt DateTime
+}
diff --git a/src/app/api/hello/route.ts b/src/app/api/hello/route.ts
deleted file mode 100644
index b500e2c..0000000
--- a/src/app/api/hello/route.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export async function GET(request: Request) {
- return new Response(":3");
-}
diff --git a/src/app/icon.svg b/src/app/icon.svg
new file mode 100644
index 0000000..408ac7b
--- /dev/null
+++ b/src/app/icon.svg
@@ -0,0 +1,191 @@
+
+
+
+
diff --git a/src/app/oauth/discord/login/route.ts b/src/app/oauth/discord/login/route.ts
new file mode 100644
index 0000000..1ad7215
--- /dev/null
+++ b/src/app/oauth/discord/login/route.ts
@@ -0,0 +1,25 @@
+import { discordRedirectUri } from "../oauth";
+
+export async function GET(request: Request) {
+ let url = `https://discord.com/oauth2/authorize`;
+
+ let randomAssString = Math.random().toString(36).substring(2, 15);
+
+ let params = new URLSearchParams();
+ params.set("response_type", "code");
+ params.set("client_id", process.env.DISCORD_CLIENT_ID);
+ params.set("scope", "guilds identify email");
+ params.set("state", randomAssString);
+ params.set("redirect_uri", discordRedirectUri());
+ params.set("prompt", "consent");
+
+ url += "?" + params.toString();
+
+ return new Response(null, {
+ status: 302,
+ headers: {
+ Location: url,
+ "Set-Cookie": `state=${randomAssString}; Path=/;`
+ }
+ });
+}
diff --git a/src/app/oauth/discord/oauth.ts b/src/app/oauth/discord/oauth.ts
new file mode 100644
index 0000000..5b1c415
--- /dev/null
+++ b/src/app/oauth/discord/oauth.ts
@@ -0,0 +1,25 @@
+import { v4 } from "uuid";
+
+export type DiscordAccessTokenResponse = {
+ access_token: string;
+ token_type: string;
+ expires_in: number;
+ refresh_token: string;
+ scope: string;
+};
+
+export function discordRedirectUri() {
+ return `${process.env.BASE_DOMAIN}oauth/discord/redirect`;
+}
+
+export async function getDiscordID(token: string) {
+ const req = await fetch("https://discord.com/api/users/@me", {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ });
+ const res: { id: string } = await req.json();
+ return res.id;
+}
+
+export const makeTicket = (): string => v4();
diff --git a/src/app/oauth/discord/redirect/route.ts b/src/app/oauth/discord/redirect/route.ts
new file mode 100644
index 0000000..5dab15e
--- /dev/null
+++ b/src/app/oauth/discord/redirect/route.ts
@@ -0,0 +1,72 @@
+import { URLSearchParams } from "url";
+import {
+ discordRedirectUri,
+ DiscordAccessTokenResponse,
+ makeTicket,
+ getDiscordID
+} from "../oauth";
+import { cookies } from "next/dist/client/components/headers";
+import prisma from "@/prisma";
+
+export async function GET(request: Request) {
+ let url = new URL(request.url);
+ let code = url.searchParams.get("code");
+ let state = url.searchParams.get("state");
+
+ if (code === null || state === null)
+ return new Response("missing code/state", { status: 400 });
+ console.log(`code: ${code}, state: ${state}`);
+
+ const cookieStore = cookies();
+ let cookieState = cookieStore.get("state");
+ // prevent forgery
+ console.log(`state: ${state}, cookieState: ${cookieState?.value}`);
+ if (cookieState?.value !== state)
+ return new Response("state is invalid", { status: 400 });
+
+ let form = new URLSearchParams();
+ form.append("client_id", process.env.DISCORD_CLIENT_ID);
+ form.append("client_secret", process.env.DISCORD_CLIENT_SECRET);
+ form.append("grant_type", "authorization_code");
+ form.append("code", code);
+ form.append("redirect_uri", discordRedirectUri());
+
+ let tokenResponse = await fetch("https://discord.com/api/oauth2/token", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded"
+ },
+ body: form.toString()
+ });
+ if (!tokenResponse.ok) throw "baby";
+ let tokenBody: DiscordAccessTokenResponse = await tokenResponse.json();
+ const id = await getDiscordID(tokenBody.access_token);
+
+ const user = await prisma.authTicket.create({
+ data: {
+ username: null,
+ ticket: makeTicket(),
+ discordAuth: {
+ connectOrCreate: {
+ where: {
+ id
+ },
+ create: {
+ id,
+ accessToken: tokenBody.access_token,
+ refreshToken: tokenBody.refresh_token,
+ refreshAt: new Date(Date.now() + tokenBody.expires_in * 1000)
+ }
+ }
+ }
+ }
+ });
+
+ return new Response(null, {
+ status: 302,
+ headers: {
+ "Set-Cookie": `ticket=${user.ticket}; Path=/;`,
+ Location: "/register"
+ }
+ });
+}
diff --git a/src/app/page.module.css b/src/app/page.module.css
index 58777be..f2da1ff 100644
--- a/src/app/page.module.css
+++ b/src/app/page.module.css
@@ -6,3 +6,22 @@
width: 100vw;
height: 100vh;
}
+
+.form {
+ display: flex;
+ flex-direction: column;
+}
+
+.form div {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
+
+.form div label {
+ margin-right: 1rem;
+}
+
+.form div input {
+ width: 15rem;
+}
\ No newline at end of file
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 76cd329..ed67766 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -3,7 +3,11 @@ import styles from "./page.module.css";
export default function Home() {
return (
:3
+ :3
+
+ login debug
+