From 7b955e84292ebbd37e39392b097246fd8efc460f Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Fri, 10 May 2024 15:34:25 +0900 Subject: [PATCH 1/5] chore: check for scopes (WIP) --- src/home/fetch-github/fetch-issues-preview.ts | 2 +- src/home/rendering/render-github-login-button.ts | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/home/fetch-github/fetch-issues-preview.ts b/src/home/fetch-github/fetch-issues-preview.ts index 2820d206..4a730c9f 100644 --- a/src/home/fetch-github/fetch-issues-preview.ts +++ b/src/home/fetch-github/fetch-issues-preview.ts @@ -24,7 +24,7 @@ async function checkPrivateRepoAccess(): Promise { } return false; } catch (error) { - if (error instanceof RequestError && error.status === 404) { + if (!!error && typeof error === "object" && "status" in error && error.status === 404) { // If the status is 404, it means the user is not a collaborator, hence no access return false; } else { diff --git a/src/home/rendering/render-github-login-button.ts b/src/home/rendering/render-github-login-button.ts index a8b5f8f9..c91d45df 100644 --- a/src/home/rendering/render-github-login-button.ts +++ b/src/home/rendering/render-github-login-button.ts @@ -1,4 +1,6 @@ +import { Octokit } from "@octokit/rest"; import { createClient } from "@supabase/supabase-js"; +import { getGitHubAccessToken } from "../getters/get-github-access-token"; import { toolbar } from "../ready-toolbar"; declare const SUPABASE_URL: string; // @DEV: passed in at build time check build/esbuild-build.ts @@ -19,10 +21,22 @@ export async function checkSupabaseSession() { } async function gitHubLoginButtonHandler() { + const scopes = "public_repo"; + // TODO check user member? + try { + console.log("1. gitHubLoginButtonHandler"); + const octokit = new Octokit({ auth: await getGitHubAccessToken() }); + console.log("2. gitHubLoginButtonHandler"); + const data = await octokit.rest.orgs.get(); + console.log("3. gitHubLoginButtonHandler"); + console.log(data); + } catch (e) { + console.error(e); + } const { error } = await supabase.auth.signInWithOAuth({ provider: "github", options: { - scopes: "repo", + scopes, }, }); if (error) { From 4f621090f0eab950e90f9194a9e3e59a890130b0 Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Fri, 10 May 2024 17:17:39 +0900 Subject: [PATCH 2/5] feat: unlock scopes button is displayed when scopes are missing --- src/home/authentication.ts | 18 ++++++++++- .../rendering/render-github-login-button.ts | 31 +++++++++---------- static/style/inverted-style.css | 5 +++ static/style/style.css | 5 +++ 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/home/authentication.ts b/src/home/authentication.ts index 7780dae6..e9d859d2 100644 --- a/src/home/authentication.ts +++ b/src/home/authentication.ts @@ -1,14 +1,30 @@ +import { Octokit } from "@octokit/rest"; import { trackDevRelReferral } from "./devrel-tracker"; import { getGitHubAccessToken } from "./getters/get-github-access-token"; import { getGitHubUser } from "./getters/get-github-user"; import { GitHubUser } from "./github-types"; import { displayGitHubUserInformation } from "./rendering/display-github-user-information"; -import { renderGitHubLoginButton } from "./rendering/render-github-login-button"; +import { renderAugmentAccessButton, renderGitHubLoginButton } from "./rendering/render-github-login-button"; + +/** + * Checks if the logged-in user is part of Ubiquity's Org, and didn't grant the 'repo' scope + */ +async function isOrgMemberWithoutScope(accessToken: string) { + const octokit = new Octokit({ auth: accessToken }); + const { headers } = await octokit.request("HEAD /"); + if (headers) { + const scopes = headers["x-oauth-scopes"]?.split(", "); + return !scopes?.includes("repo"); + } + return false; +} export async function authentication() { const accessToken = await getGitHubAccessToken(); if (!accessToken) { renderGitHubLoginButton(); + } else if (await isOrgMemberWithoutScope(accessToken)) { + renderAugmentAccessButton(); } const gitHubUser: null | GitHubUser = await getGitHubUser(); diff --git a/src/home/rendering/render-github-login-button.ts b/src/home/rendering/render-github-login-button.ts index c91d45df..e405ae79 100644 --- a/src/home/rendering/render-github-login-button.ts +++ b/src/home/rendering/render-github-login-button.ts @@ -1,6 +1,4 @@ -import { Octokit } from "@octokit/rest"; import { createClient } from "@supabase/supabase-js"; -import { getGitHubAccessToken } from "../getters/get-github-access-token"; import { toolbar } from "../ready-toolbar"; declare const SUPABASE_URL: string; // @DEV: passed in at build time check build/esbuild-build.ts @@ -20,19 +18,7 @@ export async function checkSupabaseSession() { return session; } -async function gitHubLoginButtonHandler() { - const scopes = "public_repo"; - // TODO check user member? - try { - console.log("1. gitHubLoginButtonHandler"); - const octokit = new Octokit({ auth: await getGitHubAccessToken() }); - console.log("2. gitHubLoginButtonHandler"); - const data = await octokit.rest.orgs.get(); - console.log("3. gitHubLoginButtonHandler"); - console.log(data); - } catch (e) { - console.error(e); - } +async function gitHubLoginButtonHandler(scopes = "public_repo read:org") { const { error } = await supabase.auth.signInWithOAuth({ provider: "github", options: { @@ -43,14 +29,27 @@ async function gitHubLoginButtonHandler() { console.error("Error logging in:", error); } } + +const augmentAccessButton = document.createElement("button"); +export function renderAugmentAccessButton() { + augmentAccessButton.id = "augment-access-button"; + augmentAccessButton.innerHTML = ``; + augmentAccessButton.addEventListener("click", () => gitHubLoginButtonHandler("repo read:org")); + if (toolbar) { + toolbar.appendChild(augmentAccessButton); + toolbar.classList.add("ready"); + } +} + const gitHubLoginButton = document.createElement("button"); export function renderGitHubLoginButton() { gitHubLoginButton.id = "github-login-button"; gitHubLoginButton.innerHTML = "Login With GitHub"; - gitHubLoginButton.addEventListener("click", gitHubLoginButtonHandler); + gitHubLoginButton.addEventListener("click", () => gitHubLoginButtonHandler()); if (toolbar) { toolbar.appendChild(gitHubLoginButton); toolbar.classList.add("ready"); } } + export { gitHubLoginButton }; diff --git a/static/style/inverted-style.css b/static/style/inverted-style.css index 339b33b1..6ecaaaf5 100644 --- a/static/style/inverted-style.css +++ b/static/style/inverted-style.css @@ -664,4 +664,9 @@ opacity: 0.5; } } + .svg-icon { + fill: #070707; + width: 24px; + height: 24px; + } } diff --git a/static/style/style.css b/static/style/style.css index 1664c11a..0aa12f6e 100644 --- a/static/style/style.css +++ b/static/style/style.css @@ -664,4 +664,9 @@ opacity: 0.5; } } + .svg-icon { + fill: #f8f8f8; + width: 24px; + height: 24px; + } } From db6c4703cb4e3e4fae8d58d583444265b653afdc Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Sat, 11 May 2024 20:12:10 +0900 Subject: [PATCH 3/5] chore: added div container for user info --- src/home/authentication.ts | 20 ++---------- src/home/getters/get-github-access-token.ts | 14 ++++++++ .../display-github-user-information.ts | 32 ++++++++++++------- .../rendering/render-github-login-button.ts | 7 ++-- static/style/inverted-style.css | 5 +++ static/style/style.css | 5 +++ 6 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/home/authentication.ts b/src/home/authentication.ts index e9d859d2..4e8fb1ae 100644 --- a/src/home/authentication.ts +++ b/src/home/authentication.ts @@ -1,35 +1,19 @@ -import { Octokit } from "@octokit/rest"; import { trackDevRelReferral } from "./devrel-tracker"; import { getGitHubAccessToken } from "./getters/get-github-access-token"; import { getGitHubUser } from "./getters/get-github-user"; import { GitHubUser } from "./github-types"; import { displayGitHubUserInformation } from "./rendering/display-github-user-information"; -import { renderAugmentAccessButton, renderGitHubLoginButton } from "./rendering/render-github-login-button"; - -/** - * Checks if the logged-in user is part of Ubiquity's Org, and didn't grant the 'repo' scope - */ -async function isOrgMemberWithoutScope(accessToken: string) { - const octokit = new Octokit({ auth: accessToken }); - const { headers } = await octokit.request("HEAD /"); - if (headers) { - const scopes = headers["x-oauth-scopes"]?.split(", "); - return !scopes?.includes("repo"); - } - return false; -} +import { renderGitHubLoginButton } from "./rendering/render-github-login-button"; export async function authentication() { const accessToken = await getGitHubAccessToken(); if (!accessToken) { renderGitHubLoginButton(); - } else if (await isOrgMemberWithoutScope(accessToken)) { - renderAugmentAccessButton(); } const gitHubUser: null | GitHubUser = await getGitHubUser(); if (gitHubUser) { trackDevRelReferral(gitHubUser.login + "|" + gitHubUser.id); - displayGitHubUserInformation(gitHubUser); + await displayGitHubUserInformation(gitHubUser); } } diff --git a/src/home/getters/get-github-access-token.ts b/src/home/getters/get-github-access-token.ts index 4e19f008..1187d857 100644 --- a/src/home/getters/get-github-access-token.ts +++ b/src/home/getters/get-github-access-token.ts @@ -1,7 +1,21 @@ declare const SUPABASE_STORAGE_KEY: string; // @DEV: passed in at build time check build/esbuild-build.ts +import { Octokit } from "@octokit/rest"; import { checkSupabaseSession } from "../rendering/render-github-login-button"; import { getLocalStore } from "./get-local-store"; +/** + * Checks if the logged-in user is part of Ubiquity's Org, and didn't grant the 'repo' scope + */ +export async function isOrgMemberWithoutScope() { + const octokit = new Octokit({ auth: await getGitHubAccessToken() }); + const { headers } = await octokit.request("HEAD /"); + if (headers) { + const scopes = headers["x-oauth-scopes"]?.split(", "); + return !scopes?.includes("repo"); + } + return false; +} + export async function getGitHubAccessToken(): Promise { // better to use official function, looking up localstorage has flaws const oauthToken = await checkSupabaseSession(); diff --git a/src/home/rendering/display-github-user-information.ts b/src/home/rendering/display-github-user-information.ts index cb875733..398d3a5d 100644 --- a/src/home/rendering/display-github-user-information.ts +++ b/src/home/rendering/display-github-user-information.ts @@ -1,24 +1,27 @@ +import { isOrgMemberWithoutScope } from "../getters/get-github-access-token"; import { GitHubUser } from "../github-types"; -import { getSupabase } from "./render-github-login-button"; +import { getSupabase, renderAugmentAccessButton } from "./render-github-login-button"; -export function displayGitHubUserInformation(gitHubUser: GitHubUser) { +export async function displayGitHubUserInformation(gitHubUser: GitHubUser) { const toolbar = document.getElementById("toolbar"); - const authenticated = document.createElement("div"); - authenticated.id = "authenticated"; + const authenticatedDivElement = document.createElement("div"); + const containerDivElement = document.createElement("div"); + authenticatedDivElement.id = "authenticated"; + authenticatedDivElement.classList.add("user-container"); if (!toolbar) throw new Error("toolbar not found"); const img = document.createElement("img"); img.src = gitHubUser.avatar_url; img.alt = gitHubUser.login; - authenticated.appendChild(img); + authenticatedDivElement.appendChild(img); - const div = document.createElement("div"); + const divNameElement = document.createElement("div"); - div.textContent = gitHubUser.name; - div.classList.add("full"); - authenticated.appendChild(div); + divNameElement.textContent = gitHubUser.name; + divNameElement.classList.add("full"); + authenticatedDivElement.appendChild(divNameElement); - authenticated.addEventListener("click", async function signOut() { + authenticatedDivElement.addEventListener("click", async function signOut() { const supabase = getSupabase(); const { error } = await supabase.auth.signOut(); if (error) { @@ -28,7 +31,14 @@ export function displayGitHubUserInformation(gitHubUser: GitHubUser) { window.location.reload(); }); - toolbar.appendChild(authenticated); + containerDivElement.appendChild(authenticatedDivElement); + + if (await isOrgMemberWithoutScope()) { + const accessButton = renderAugmentAccessButton(); + containerDivElement.appendChild(accessButton); + } + + toolbar.appendChild(containerDivElement); toolbar.setAttribute("data-authenticated", "true"); toolbar.classList.add("ready"); } diff --git a/src/home/rendering/render-github-login-button.ts b/src/home/rendering/render-github-login-button.ts index e405ae79..2cec107d 100644 --- a/src/home/rendering/render-github-login-button.ts +++ b/src/home/rendering/render-github-login-button.ts @@ -33,12 +33,9 @@ async function gitHubLoginButtonHandler(scopes = "public_repo read:org") { const augmentAccessButton = document.createElement("button"); export function renderAugmentAccessButton() { augmentAccessButton.id = "augment-access-button"; - augmentAccessButton.innerHTML = ``; + augmentAccessButton.innerHTML = ``; augmentAccessButton.addEventListener("click", () => gitHubLoginButtonHandler("repo read:org")); - if (toolbar) { - toolbar.appendChild(augmentAccessButton); - toolbar.classList.add("ready"); - } + return augmentAccessButton; } const gitHubLoginButton = document.createElement("button"); diff --git a/static/style/inverted-style.css b/static/style/inverted-style.css index 6ecaaaf5..7990a2e7 100644 --- a/static/style/inverted-style.css +++ b/static/style/inverted-style.css @@ -669,4 +669,9 @@ width: 24px; height: 24px; } + .user-container { + display: flex; + align-items: center; + margin-right: 8px; + } } diff --git a/static/style/style.css b/static/style/style.css index 0aa12f6e..942f24f8 100644 --- a/static/style/style.css +++ b/static/style/style.css @@ -669,4 +669,9 @@ width: 24px; height: 24px; } + .user-container { + display: flex; + align-items: center; + margin-right: 8px; + } } From 32b1add87ed5a3d14e982548e157f256e297c477 Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Mon, 13 May 2024 11:17:06 +0900 Subject: [PATCH 4/5] chore: added 401 as an error for accessing private repo --- src/home/fetch-github/fetch-issues-preview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/home/fetch-github/fetch-issues-preview.ts b/src/home/fetch-github/fetch-issues-preview.ts index 4a730c9f..fe75e99b 100644 --- a/src/home/fetch-github/fetch-issues-preview.ts +++ b/src/home/fetch-github/fetch-issues-preview.ts @@ -24,7 +24,7 @@ async function checkPrivateRepoAccess(): Promise { } return false; } catch (error) { - if (!!error && typeof error === "object" && "status" in error && error.status === 404) { + if (!!error && typeof error === "object" && "status" in error && (error.status === 404 || error.status === 401)) { // If the status is 404, it means the user is not a collaborator, hence no access return false; } else { From b4f9847b02ae62ec1d157b0c238f7af8c2501056 Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Mon, 13 May 2024 12:05:21 +0900 Subject: [PATCH 5/5] chore: proper check org for authenticated user --- src/home/getters/get-github-access-token.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/home/getters/get-github-access-token.ts b/src/home/getters/get-github-access-token.ts index 1187d857..c5303f81 100644 --- a/src/home/getters/get-github-access-token.ts +++ b/src/home/getters/get-github-access-token.ts @@ -8,6 +8,16 @@ import { getLocalStore } from "./get-local-store"; */ export async function isOrgMemberWithoutScope() { const octokit = new Octokit({ auth: await getGitHubAccessToken() }); + try { + await octokit.orgs.getMembershipForAuthenticatedUser({ + org: "ubiquity", + }); + } catch (e) { + if (e && typeof e === "object" && "status" in e && e.status === 404) { + return false; + } + throw e; + } const { headers } = await octokit.request("HEAD /"); if (headers) { const scopes = headers["x-oauth-scopes"]?.split(", ");