Skip to content

Commit

Permalink
feat: add gitlab oauth (#15)
Browse files Browse the repository at this point in the history
Resolved issue #8
  • Loading branch information
Abhijna-Raghavendra authored Feb 29, 2024
2 parents c6259b1 + e6b244f commit e5fa775
Show file tree
Hide file tree
Showing 21 changed files with 202 additions and 80 deletions.
66 changes: 54 additions & 12 deletions src/backend/auth/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,65 @@ 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) {
await authenticateAndCreateJWT(ctx, id, secret, "gitlab");
}

async function authenticateAndCreateJWT(
ctx: Context,
id: string,
secret: string,
provider: string
) {
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({
const rootUrl = new URL(oauthUrl);
rootUrl.search = provider === "github"? new URLSearchParams({
client_id: id,
client_secret: secret,
code,
}).toString();
}).toString() : provider === "gitlab"? new URLSearchParams({
client_id: id,
client_secret: secret,
code,
grant_type: "authorization_code",
redirect_uri: "http://localhost:7777/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";
Expand All @@ -37,13 +71,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 };
12 changes: 6 additions & 6 deletions src/backend/db.ts
Original file line number Diff line number Diff line change
@@ -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")!;
Expand Down Expand Up @@ -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,
},
},
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/backend/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Session } from "https://deno.land/x/[email protected]/mod.ts";
import { create, verify } from "https://deno.land/x/[email protected]/mod.ts";
import { exec } from "https://deno.land/x/[email protected]/mod.ts";
import * as Sentry from "npm:@sentry/node";
import { oakCors } from "https://deno.land/x/[email protected]/mod.ts";

export {
Application,
Expand All @@ -20,5 +21,5 @@ export {
Sentry,
Session,
Status,
verify,
verify, oakCors,
};
11 changes: 8 additions & 3 deletions src/backend/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
20 changes: 14 additions & 6 deletions src/backend/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ import {
Router,
Sentry,
Session,
Status,
Status, oakCors
} 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")!;

Sentry.init({
Expand Down Expand Up @@ -44,12 +46,18 @@ 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)
)
.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 });
Expand Down
1 change: 0 additions & 1 deletion src/backend/utils/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
34 changes: 34 additions & 0 deletions src/backend/utils/get-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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");
}
}
9 changes: 0 additions & 9 deletions src/backend/utils/github-user.ts

This file was deleted.

8 changes: 4 additions & 4 deletions src/backend/utils/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Expand Down
7 changes: 5 additions & 2 deletions src/frontend/.env.sample
Original file line number Diff line number Diff line change
@@ -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
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=...
3 changes: 2 additions & 1 deletion src/frontend/src/components/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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);
</script>
Expand Down
10 changes: 8 additions & 2 deletions src/frontend/src/components/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,14 @@
import { authorize } from '../utils/authorize';
import { useRouter } from "vue-router";
const code = useRouter().currentRoute.value.query.code;
authorize(code);
const route = useRouter().currentRoute.value;
const code = route.query.code;
const provider = localStorage.getItem("provider");
if (code && provider) {
authorize(code, provider);
}
</script>
<script>
import loginmodal from './loginmodal.vue';
Expand Down
9 changes: 3 additions & 6 deletions src/frontend/src/components/loginmodal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</template>

<script setup>
import { githubUrl } from '../utils/github-url.ts';
import { oauthUrl } from '../utils/oauth-urls';
</script>

<script>
Expand All @@ -31,11 +31,8 @@ export default {
window.location.reload();
},
loginWith(service) {
if (service === 'github') {
window.location.href = githubUrl();
} else if (service === 'gitlab') {
// Implement Gitlab login logic
}
localStorage.setItem("provider", service);
window.location.href = oauthUrl(service);
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createApp, Vue } from "vue";
import "./style.css";
import App from "./App.vue";
import router from "./router/index.ts";
import router from "./router/index";

const app = createApp(App);
app.use(router);
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ const routes = [
from: RouteLocationNormalized,
next: NavigationGuardNext,
) {
if (!localStorage.getItem("JWTUser")) next({ name: "Login" });
if (!localStorage.getItem("JWTUser") || !localStorage.getItem("provider")) next({ name: "Login" });
else {
const user = await check_jwt(localStorage.getItem("JWTUser")!);
const user = await check_jwt(localStorage.getItem("JWTUser")!,localStorage.getItem("provider")!);
console.log(user);
if (user == "") next({ name: "Login" });
else next();
Expand Down
Loading

0 comments on commit e5fa775

Please sign in to comment.