diff --git a/src/backend/.env.sample b/src/backend/.env.sample index a446357..3070d10 100644 --- a/src/backend/.env.sample +++ b/src/backend/.env.sample @@ -1,5 +1,8 @@ GITHUB_OAUTH_CLIENT_ID=... GITHUB_OAUTH_CLIENT_SECRET=... +GITLAB_OAUTH_CLIENT_ID=... +GITLAB_OAUTH_CLIENT_SECRET=... MONGO_API_KEY=... MONGO_APP_ID=... -SENTRY_DSN=... \ No newline at end of file +SENTRY_DSN=... +FRONTEND=... \ No newline at end of file diff --git a/src/backend/auth/github.ts b/src/backend/auth/github.ts index af2d3e6..6044a97 100644 --- a/src/backend/auth/github.ts +++ b/src/backend/auth/github.ts @@ -3,31 +3,74 @@ import { checkUser } from "../db.ts"; import { checkJWT, createJWT } from "../utils/jwt.ts"; async function githubAuth(ctx: Context, id: string, secret: string) { + await authenticateAndCreateJWT(ctx, id, secret, "github"); +} + +async function gitlabAuth( + ctx: Context, + id: string, + secret: string, + frontend: string, +) { + await authenticateAndCreateJWT(ctx, id, secret, "gitlab", frontend); +} + +async function authenticateAndCreateJWT( + ctx: Context, + id: string, + secret: string, + provider: string, + frontend = "", +) { if (!ctx.request.hasBody) { ctx.throw(415); } const code = await ctx.request.body().value; - console.log(code); - const rootUrl = new URL("https://github.com/login/oauth/access_token"); + const oauthUrl = provider === "github" + ? "https://github.com/login/oauth/access_token" + : provider === "gitlab" + ? "https://gitlab.com/oauth/token" + : null; + + if (oauthUrl === null) { + ctx.response.body = "Unsupported provider"; + return; + } if (code !== null) { - rootUrl.search = new URLSearchParams({ - client_id: id, - client_secret: secret, - code, - }).toString(); + const rootUrl = new URL(oauthUrl); + rootUrl.search = provider === "github" + ? new URLSearchParams({ + client_id: id, + client_secret: secret, + code, + }).toString() + : provider === "gitlab" + ? new URLSearchParams({ + client_id: id, + client_secret: secret, + code, + grant_type: "authorization_code", + redirect_uri: `${frontend}/login`, + }).toString() + : ""; + const resp = await fetch(rootUrl.toString(), { method: "POST", headers: { - "Accept": "application/json", + Accept: "application/json", }, }); + const body = await resp.json(); - const { status, githubId } = await checkUser(body.access_token); + + const { status, userId } = await checkUser(body.access_token, provider); + ctx.response.headers.set("Access-Control-Allow-Origin", "*"); + if (status.matchedCount == 1) { - const id_jwt = await createJWT(githubId); - Sentry.captureMessage("User " + githubId + " logged in", "info"); + const id_jwt = await createJWT(provider, userId); + Sentry.captureMessage("User " + userId + " logged in", "info"); ctx.response.body = id_jwt; } else { ctx.response.body = "not authorized"; @@ -37,13 +80,21 @@ async function githubAuth(ctx: Context, id: string, secret: string) { } } -async function githubId(ctx: Context) { +async function handleJwtAuthentication(ctx: Context) { ctx.response.headers.set("Access-Control-Allow-Origin", "*"); if (!ctx.request.hasBody) { ctx.throw(415); } - const jwt_token = await ctx.request.body().value; - ctx.response.body = await checkJWT(jwt_token); + const body = await ctx.request.body().value; + let document; + try { + document = JSON.parse(body); + } catch (e) { + document = body; + } + const jwt_token = document.jwt_token; + const provider = document.provider; + ctx.response.body = await checkJWT(provider, jwt_token); } -export { githubAuth, githubId }; +export { githubAuth, gitlabAuth, handleJwtAuthentication }; diff --git a/src/backend/db.ts b/src/backend/db.ts index b127480..c5b4814 100644 --- a/src/backend/db.ts +++ b/src/backend/db.ts @@ -1,4 +1,4 @@ -import getGithubUser from "./utils/github-user.ts"; +import getProviderUser from "./utils/get-user.ts"; import DfContentMap from "./types/maps_interface.ts"; const DATA_API_KEY = Deno.env.get("MONGO_API_KEY")!; @@ -27,17 +27,17 @@ const MONGO_URLs = { }; // Function to update access token on db if user exists -async function checkUser(accessToken: string) { - const githubId = await getGithubUser(accessToken); +async function checkUser(accessToken: string, provider: string) { + const userId = await getProviderUser(accessToken, provider); const query = { collection: "user_auth", database: DATABASE, dataSource: DATA_SOURCE, - filter: { "githubId": githubId }, + filter: { [`${provider}Id`]: userId }, update: { $set: { - "githubId": githubId, + [`${provider}Id`]: userId, "authToken": accessToken, }, }, @@ -47,7 +47,7 @@ async function checkUser(accessToken: string) { const status_resp = await fetch(MONGO_URLs.update.toString(), options); const status = await status_resp.json(); - return { status, githubId }; + return { status, userId }; } // Get all content maps corresponding to user diff --git a/src/backend/dependencies.ts b/src/backend/dependencies.ts index 6bb44b6..3bf13fa 100644 --- a/src/backend/dependencies.ts +++ b/src/backend/dependencies.ts @@ -9,6 +9,7 @@ import { Session } from "https://deno.land/x/oak_sessions@v4.1.9/mod.ts"; import { create, verify } from "https://deno.land/x/djwt@v2.9.1/mod.ts"; import { exec } from "https://deno.land/x/exec@0.0.5/mod.ts"; import * as Sentry from "npm:@sentry/node"; +import { oakCors } from "https://deno.land/x/cors@v1.2.2/mod.ts"; export { Application, @@ -16,6 +17,7 @@ export { create, exec, isHttpError, + oakCors, Router, Sentry, Session, diff --git a/src/backend/main.ts b/src/backend/main.ts index d741412..5dfc1ea 100644 --- a/src/backend/main.ts +++ b/src/backend/main.ts @@ -6,7 +6,8 @@ import { addMaps, deleteMaps, getMaps } from "./db.ts"; async function getSubdomains(ctx: Context) { const author = ctx.request.url.searchParams.get("user"); const token = ctx.request.url.searchParams.get("token"); - if (author != await checkJWT(token!)) { + const provider = ctx.request.url.searchParams.get("provider"); + if (author != await checkJWT(provider!, token!)) { ctx.throw(401); } const data = await getMaps(author); @@ -27,13 +28,15 @@ async function addSubdomain(ctx: Context) { } const copy = { ...document }; const token = document.token; + const provider = document.provider; delete document.token; + delete document.provider; delete document.port; delete document.build_cmds; delete document.stack; delete document.env_content; delete document.static_content; - if (document.author != await checkJWT(token)) { + if (document.author != await checkJWT(provider, token)) { ctx.throw(401); } const success: boolean = await addMaps(document); @@ -71,8 +74,10 @@ async function deleteSubdomain(ctx: Context) { } const author = document.author; const token = document.token; + const provider = document.provider; delete document.token; - if (author != await checkJWT(token)) { + delete document.provider; + if (author != await checkJWT(provider, token)) { ctx.throw(401); } const data = await deleteMaps(document); diff --git a/src/backend/server.ts b/src/backend/server.ts index 8f852b7..ccbd303 100644 --- a/src/backend/server.ts +++ b/src/backend/server.ts @@ -2,21 +2,29 @@ import { Application, Context, isHttpError, + oakCors, Router, Sentry, Session, Status, } from "./dependencies.ts"; -import { githubAuth, githubId } from "./auth/github.ts"; +import { + githubAuth, + gitlabAuth, + handleJwtAuthentication, +} from "./auth/github.ts"; import { addSubdomain, deleteSubdomain, getSubdomains } from "./main.ts"; const router = new Router(); const app = new Application(); const PORT = 7000; -const id: string = Deno.env.get("GITHUB_OAUTH_CLIENT_ID")!; -const secret: string = Deno.env.get("GITHUB_OAUTH_CLIENT_SECRET")!; +const githubClientId: string = Deno.env.get("GITHUB_OAUTH_CLIENT_ID")!; +const githubClientSecret: string = Deno.env.get("GITHUB_OAUTH_CLIENT_SECRET")!; +const gitlabClientId: string = Deno.env.get("GITLAB_OAUTH_CLIENT_ID")!; +const gitlabClientSecret: string = Deno.env.get("GITLAB_OAUTH_CLIENT_SECRET")!; const dsn: string = Deno.env.get("SENTRY_DSN")!; +const frontend: string = Deno.env.get("FRONTEND")!; Sentry.init({ dsn: dsn, @@ -44,12 +52,20 @@ app.use(async (ctx: Context, next) => { app.use(Session.initMiddleware()); router - .post("/auth/github", (ctx) => githubAuth(ctx, id, secret)) - .post("/auth/jwt", (ctx) => githubId(ctx)) + .post( + "/auth/github", + (ctx) => githubAuth(ctx, githubClientId, githubClientSecret), + ) + .post( + "/auth/gitlab", + (ctx) => gitlabAuth(ctx, gitlabClientId, gitlabClientSecret, frontend), + ) + .post("/auth/jwt", (ctx) => handleJwtAuthentication(ctx)) .get("/map", (ctx) => getSubdomains(ctx)) .post("/map", (ctx) => addSubdomain(ctx)) .post("/mapdel", (ctx) => deleteSubdomain(ctx)); +app.use(oakCors()); app.use(router.routes()); app.use(router.allowedMethods()); app.listen({ port: PORT }); diff --git a/src/backend/utils/container.ts b/src/backend/utils/container.ts index 3a1118b..f3b2d04 100644 --- a/src/backend/utils/container.ts +++ b/src/backend/utils/container.ts @@ -17,7 +17,6 @@ export default function dockerize( dockerfile = "FROM node:latest \n WORKDIR /app \n COPY ./package*.json . \n RUN npm install \n COPY . ." + build_cmds_mapped + `\n EXPOSE ${port} \n` + execute_cmd; - console.log(port); } return dockerfile.toString(); } diff --git a/src/backend/utils/get-user.ts b/src/backend/utils/get-user.ts new file mode 100644 index 0000000..b795d6a --- /dev/null +++ b/src/backend/utils/get-user.ts @@ -0,0 +1,37 @@ +export default async function getProviderUser( + accessToken: string, + provider: string, +) { + let apiUrl = ""; + let authorizationHeader = ""; + + if (provider === "github") { + apiUrl = "https://api.github.com/user"; + authorizationHeader = `Bearer ${accessToken}`; + } else if (provider === "gitlab") { + apiUrl = "https://gitlab.com/api/v4/user"; + authorizationHeader = `Bearer ${accessToken}`; + } else { + throw new Error("Unsupported provider"); + } + + const user_resp = await fetch(apiUrl, { + headers: { + Authorization: authorizationHeader, + }, + }); + + if (user_resp.status !== 200) { + throw new Error(`Failed to fetch user data from ${provider}`); + } + + const user = await user_resp.json(); + + if (provider === "github") { + return user.login; + } else if (provider === "gitlab") { + return user.username; + } else { + throw new Error("Unsupported provider"); + } +} diff --git a/src/backend/utils/github-user.ts b/src/backend/utils/github-user.ts deleted file mode 100644 index e7831aa..0000000 --- a/src/backend/utils/github-user.ts +++ /dev/null @@ -1,9 +0,0 @@ -export default async function getGithubUser(accessToken: string) { - const user_resp = await fetch("https://api.github.com/user", { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }); - const user = await user_resp.json(); - return user.login; -} diff --git a/src/backend/utils/jwt.ts b/src/backend/utils/jwt.ts index 6446d5c..34e2779 100644 --- a/src/backend/utils/jwt.ts +++ b/src/backend/utils/jwt.ts @@ -6,17 +6,17 @@ const key = await crypto.subtle.generateKey( ["sign", "verify"], ); -async function createJWT(githubId: string) { +async function createJWT(provider: string, githubId: string) { const token = await create({ alg: "HS512", typ: "JWT" }, { - githubId: githubId, + [`${provider}Id`]: githubId, }, key); return token; } -async function checkJWT(token: string) { +async function checkJWT(provider: string, token: string) { try { const payload = await verify(token, key); - return payload.githubId!; + return payload[`${provider}Id`]!; } catch (error) { return "not verified"; } diff --git a/src/frontend/.env.sample b/src/frontend/.env.sample index c242f3f..5e4e420 100644 --- a/src/frontend/.env.sample +++ b/src/frontend/.env.sample @@ -1,4 +1,7 @@ VITE_APP_GITHUB_OAUTH_CLIENT_ID=... VITE_APP_GITHUB_OAUTH_CLIENT_SECRET=... -VITE_APP_GITHUB_OAUTH_REDIRECT_URL=http://localhost:XXXX/login -VITE_APP_BACKEND_PORT=XXXX \ No newline at end of file +VITE_APP_GITHUB_OAUTH_REDIRECT_URL=.../login +VITE_APP_GITLAB_OAUTH_CLIENT_ID=... +VITE_APP_GITLAB_OAUTH_CLIENT_SECRET=... +VITE_APP_GITLAB_OAUTH_REDIRECT_URL=.../login +VITE_APP_BACKEND=... \ No newline at end of file diff --git a/src/frontend/public/df-logo.png b/src/frontend/public/df-logo.png new file mode 100644 index 0000000..b43603a Binary files /dev/null and b/src/frontend/public/df-logo.png differ diff --git a/src/frontend/public/github-logo.png b/src/frontend/public/github-logo.png new file mode 100644 index 0000000..6cb3b70 Binary files /dev/null and b/src/frontend/public/github-logo.png differ diff --git a/src/frontend/public/gitlab-logo.png b/src/frontend/public/gitlab-logo.png new file mode 100644 index 0000000..cd269a8 Binary files /dev/null and b/src/frontend/public/gitlab-logo.png differ diff --git a/src/frontend/src/components/404.vue b/src/frontend/src/components/404.vue index de44904..6cee6b0 100644 --- a/src/frontend/src/components/404.vue +++ b/src/frontend/src/components/404.vue @@ -1,7 +1,10 @@ - - 404 - Page Not Found - The page you're looking for doesn't exist - + + 404 + Page Not Found + The page you're looking for doesn't exist + + \ No newline at end of file diff --git a/src/frontend/src/components/Home.vue b/src/frontend/src/components/Home.vue index f789864..b9ebffe 100644 --- a/src/frontend/src/components/Home.vue +++ b/src/frontend/src/components/Home.vue @@ -2,7 +2,8 @@ import { getMaps } from '../utils/maps.ts'; import { check_jwt } from '../utils/authorize.ts'; const token = localStorage.getItem("JWTUser"); -const user = await check_jwt(token); +const provider = localStorage.getItem("provider"); +const user = await check_jwt(token, provider); const fields = ["date", "subdomain", "resource", "resource_type", ""]; const maps = await getMaps(user); @@ -11,9 +12,15 @@ const maps = await getMaps(user); - Domain Forge + + + Domain Forge + Docs + + Logout + @@ -67,19 +74,38 @@ export default { selectedItem : null, } }, + methods: { + logoutAndRedirect() { + localStorage.clear(); + this.$router.push({ path: '/login' }); + } + } } + + \ No newline at end of file diff --git a/src/frontend/src/components/Login.vue b/src/frontend/src/components/Login.vue index 11bc9b3..5d53cdb 100644 --- a/src/frontend/src/components/Login.vue +++ b/src/frontend/src/components/Login.vue @@ -2,11 +2,14 @@ - Domain Forge + + + Domain Forge + Docs - Login with Github + Login @@ -18,6 +21,7 @@ Empower your online presence with Domain Forge Subdomain generation integrated with robust hosting infrastructure for seamless website management +
Domain Forge