From 568d894061ea8d47dc2bcbcfb9e16591627709f3 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sun, 28 Dec 2025 21:24:44 +0100 Subject: [PATCH] Github auth --- package-lock.json | 153 +++++++++++++++++++++++++++++ package.json | 3 + src/app/api/auth/callback/route.ts | 49 +++++++++ src/app/page.tsx | 41 ++------ src/core/auth/oauth.ts | 13 +++ src/core/auth/redirect.ts | 14 +++ src/core/auth/session.ts | 7 ++ 7 files changed, 247 insertions(+), 33 deletions(-) create mode 100644 src/app/api/auth/callback/route.ts create mode 100644 src/core/auth/oauth.ts create mode 100644 src/core/auth/redirect.ts create mode 100644 src/core/auth/session.ts diff --git a/package-lock.json b/package-lock.json index a3f93d7..a1f74dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,11 +16,13 @@ "@radix-ui/react-tooltip": "^1.2.8", "@types/dockerode": "^3.3.47", "@types/js-cookie": "^3.0.6", + "@types/simple-oauth2": "^5.0.8", "axios": "^1.13.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dockerode": "^4.0.9", "http-status-codes": "^2.3.0", + "iron-session": "^8.0.4", "js-cookie": "^3.0.5", "lucide-react": "^0.562.0", "next": "16.1.1", @@ -28,6 +30,7 @@ "react": "19.2.3", "react-dom": "19.2.3", "recharts": "^2.15.4", + "simple-oauth2": "^5.1.0", "systeminformation": "^5.28.2", "tailwind-merge": "^3.4.0" }, @@ -166,6 +169,53 @@ "node": ">=6" } }, + "node_modules/@hapi/boom": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz", + "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@hapi/bourne": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz", + "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/hoek": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", + "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@hapi/topo/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/wreck": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/@hapi/wreck/-/wreck-18.1.0.tgz", + "integrity": "sha512-0z6ZRCmFEfV/MQqkQomJ7sl/hyxvcZM7LtuVqN3vdAO4vM9eBbowl0kaqQj9EJJQab+3Uuh1GxbGIBFy4NfJ4w==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/boom": "^10.0.1", + "@hapi/bourne": "^3.0.0", + "@hapi/hoek": "^11.0.2" + } + }, "node_modules/@img/colour": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", @@ -1818,6 +1868,33 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/address/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -2217,6 +2294,12 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/simple-oauth2": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@types/simple-oauth2/-/simple-oauth2-5.0.8.tgz", + "integrity": "sha512-TehQqoOGdy3/rmFsCEGgnt1f4JhUCA0joWemGGCTbVYvoZvfBjkRsBFYmz8k0V/sn2XQZHe33L4lWxqMhIO3tQ==", + "license": "MIT" + }, "node_modules/@types/ssh2": { "version": "1.15.5", "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz", @@ -2495,6 +2578,15 @@ "node": ">= 0.8" } }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cpu-features": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", @@ -3102,6 +3194,30 @@ "node": ">=12" } }, + "node_modules/iron-session": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/iron-session/-/iron-session-8.0.4.tgz", + "integrity": "sha512-9ivNnaKOd08osD0lJ3i6If23GFS2LsxyMU8Gf/uBUEgm8/8CC1hrrCHFDpMo3IFbpBgwoo/eairRsaD3c5itxA==", + "funding": [ + "https://github.com/sponsors/vvo", + "https://github.com/sponsors/brc-dd" + ], + "license": "MIT", + "dependencies": { + "cookie": "^0.7.2", + "iron-webcrypto": "^1.2.1", + "uncrypto": "^0.1.3" + } + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -3139,6 +3255,25 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/joi/node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, "node_modules/js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", @@ -4124,6 +4259,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-oauth2": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/simple-oauth2/-/simple-oauth2-5.1.0.tgz", + "integrity": "sha512-gWDa38Ccm4MwlG5U7AlcJxPv3lvr80dU7ARJWrGdgvOKyzSj1gr3GBPN1rABTedAYvC/LsGYoFuFxwDBPtGEbw==", + "license": "Apache-2.0", + "dependencies": { + "@hapi/hoek": "^11.0.4", + "@hapi/wreck": "^18.0.0", + "debug": "^4.3.4", + "joi": "^17.6.4" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4353,6 +4500,12 @@ "node": ">=14.17" } }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", diff --git a/package.json b/package.json index 59f0325..05246f9 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,13 @@ "@radix-ui/react-tooltip": "^1.2.8", "@types/dockerode": "^3.3.47", "@types/js-cookie": "^3.0.6", + "@types/simple-oauth2": "^5.0.8", "axios": "^1.13.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dockerode": "^4.0.9", "http-status-codes": "^2.3.0", + "iron-session": "^8.0.4", "js-cookie": "^3.0.5", "lucide-react": "^0.562.0", "next": "16.1.1", @@ -28,6 +30,7 @@ "react": "19.2.3", "react-dom": "19.2.3", "recharts": "^2.15.4", + "simple-oauth2": "^5.1.0", "systeminformation": "^5.28.2", "tailwind-merge": "^3.4.0" }, diff --git a/src/app/api/auth/callback/route.ts b/src/app/api/auth/callback/route.ts new file mode 100644 index 0000000..24b3ec5 --- /dev/null +++ b/src/app/api/auth/callback/route.ts @@ -0,0 +1,49 @@ +import axios from "axios"; +import { StatusCodes } from "http-status-codes"; +import { cookies } from "next/headers"; +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + const code = searchParams.get("code"); + + if (!code) { + return NextResponse.json( + { error: "No code provided" }, + { status: StatusCodes.BAD_REQUEST } + ); + } + + try { + const response = await axios.post( + "https://github.com/login/oauth/access_token", + { + client_id: process.env.GITHUB_CLIENT_ID, + client_secret: process.env.GITHUB_CLIENT_SECRET, + code, + }, + { + headers: { + Accept: "application/json", + }, + } + ); + + const accessToken = response.data.access_token; + + const c = await cookies(); + c.set("github_access", accessToken, { + httpOnly: true, + path: "/", + secure: process.env.NODE_ENV === "production", + }); + + return NextResponse.redirect(new URL("/overview", request.url)); + } catch (error) { + console.log(error); + return NextResponse.json( + { error: "Error exchanging code for access token" }, + { status: StatusCodes.INTERNAL_SERVER_ERROR } + ); + } +} diff --git a/src/app/page.tsx b/src/app/page.tsx index a134a83..60fd85d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,16 +2,13 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; +import redirectAuth from "@/core/auth/redirect"; import Image from "next/image"; -import { useState } from "react"; export default function LogIn() { - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - - const logIn = async () => {}; + const withGithub = () => { + redirectAuth(); + }; return (
@@ -25,34 +22,12 @@ export default function LogIn() { height={80} /> - Log in + {/* Log in */} -
-
- - setUsername(e.target.value)} - /> -
- -
- - setPassword(e.target.value)} - /> -
- - -
+
diff --git a/src/core/auth/oauth.ts b/src/core/auth/oauth.ts new file mode 100644 index 0000000..2157e50 --- /dev/null +++ b/src/core/auth/oauth.ts @@ -0,0 +1,13 @@ +import { AuthorizationCode } from "simple-oauth2"; + +export const client = new AuthorizationCode({ + client: { + id: process.env.GITHUB_CLIENT_ID!, + secret: process.env.GITHUB_CLIENT_SECRET!, + }, + auth: { + tokenHost: "https://github.com", + tokenPath: "/login/oauth/access_token", + authorizePath: "/login/oauth/authorize", + }, +}); diff --git a/src/core/auth/redirect.ts b/src/core/auth/redirect.ts new file mode 100644 index 0000000..b3ed3f7 --- /dev/null +++ b/src/core/auth/redirect.ts @@ -0,0 +1,14 @@ +"use server"; + +import { client } from "@/core/auth/oauth"; +import { redirect } from "next/navigation"; + +export default async function redirectAuth() { + const authorizationUri = client.authorizeURL({ + redirect_uri: "http://localhost:3000/api/auth/callback", + scope: "user:email", + state: "random_state_string", + }); + + redirect(authorizationUri); +} diff --git a/src/core/auth/session.ts b/src/core/auth/session.ts new file mode 100644 index 0000000..8cddeb5 --- /dev/null +++ b/src/core/auth/session.ts @@ -0,0 +1,7 @@ +import { getIronSession } from "iron-session"; + +export const session = getIronSession({ + password: process.env.SESSION_PASSWORD!, + cookieName: "nextjs_oauth_session", + secure: process.env.NODE_ENV === "production", +});