diff --git a/_dot_vercel_copy/output/functions/api.func/router.js b/_dot_vercel_copy/output/functions/api.func/router.js index 69960271..e03265d8 100644 --- a/_dot_vercel_copy/output/functions/api.func/router.js +++ b/_dot_vercel_copy/output/functions/api.func/router.js @@ -9,6 +9,7 @@ import { default as statusUp } from "./api-renamed/status/up.js"; import { default as authenticate } from "./api-renamed/authenticate.js"; import { default as deleteUser } from "./api-renamed/delete-user.js"; import { default as privateAccess } from "./api-renamed/private-access.js"; +import { default as downgrade } from "./api-renamed/downgrade.js"; export default async (req, res) => { // remaining code expects express.js-like request and response objects @@ -59,6 +60,9 @@ export default async (req, res) => { case "/api/private-access": privateAccess(req, res); break; + case "/api/downgrade": + downgrade(req, res); + break; default: res.statusCode = 404; res.end("Not Found"); diff --git a/_dot_vercel_copy/output/functions/api/downgrade.func b/_dot_vercel_copy/output/functions/api/downgrade.func new file mode 120000 index 00000000..2e79e533 --- /dev/null +++ b/_dot_vercel_copy/output/functions/api/downgrade.func @@ -0,0 +1 @@ +../api.func \ No newline at end of file diff --git a/api-renamed/downgrade.js b/api-renamed/downgrade.js new file mode 100644 index 00000000..34d7714e --- /dev/null +++ b/api-renamed/downgrade.js @@ -0,0 +1,74 @@ +import { hasPrivateAccess, getUserToken, deleteUser } from "../src/common/database.js"; +import axios from "axios"; +import { logger } from "../src/index.js"; + +export default async (req, res) => { + // We could optimize this method by doing all 3 database operations in one statement, using "DELETE ... RETURNING ..." + + const { user_key } = req.query; + if (!user_key) { + res.statusCode = 400; + res.send("missing user_key"); + return; + } + + if ( + !process.env.OAUTH_CLIENT_ID || + !process.env.OAUTH_CLIENT_SECRET || + !process.env.OAUTH_REDIRECT_URI + ) { + throw new Error( + "OAuth Error: One or more required environment variables (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_REDIRECT_URI) are not set.", + ); + } + + // verify that user has private access + const privateAccess = await hasPrivateAccess(user_key); + if (!privateAccess) { + res.statusCode = 400; + res.send("user does not have private access"); + return; + } + + // get access token for user + const token = await getUserToken(user_key); + if (!token) { + res.statusCode = 404; + res.send("user not found"); + return; + } + + // delete existing app authorization via GitHub API + try { + await axios.delete( + `https://api.github.com/applications/${process.env.OAUTH_CLIENT_ID}/grant`, + { + auth: { + username: process.env.OAUTH_CLIENT_ID, + password: process.env.OAUTH_CLIENT_SECRET, + }, + data: { access_token: token }, + headers: { + Accept: "application/vnd.github+json", + }, + }, + ); + } catch (err) { + logger.error(err); + res.statusCode = 500; + res.send("Failed to delete GitHub authorization with private access"); + return; + } + + await deleteUser(user_key); + + // redirect to GitHub OAuth for public access + const params = new URLSearchParams({ + client_id: process.env.OAUTH_CLIENT_ID, + redirect_uri: process.env.OAUTH_REDIRECT_URI, + }).toString(); + + res.statusCode = 302; + res.setHeader("Location", `https://github.com/login/oauth/authorize?${params}`); + res.end(); +}; diff --git a/src/common/database.js b/src/common/database.js index 394136e9..08aeca37 100644 --- a/src/common/database.js +++ b/src/common/database.js @@ -222,3 +222,35 @@ export async function hasPrivateAccess(userKey) { } } } + +/** + * Fetches access_token for a given user_key. + * + * @param {string} userKey user key of the user to fetch token for + * @returns Returns user key if found, null otherwise + */ +export async function getUserToken(userKey) { + if (!pool) { + return null; + } + + const query = ` + SELECT access_token + FROM authenticated_users + WHERE user_key = $1 + LIMIT 1 + `; + try { + const { rows } = await pool.query(query, [userKey]); + if (rows.length === 0) { + return null; + } + return rows[0].access_token; + } catch (err) { + if (err.code === "42P01") { + return null; + } else { + throw err; + } + } +} diff --git a/src/users.js b/src/users.js index cb65b89a..a71c302f 100644 --- a/src/users.js +++ b/src/users.js @@ -30,7 +30,7 @@ async function githubAuthenticate(code) { !process.env.OAUTH_CLIENT_SECRET || !process.env.OAUTH_REDIRECT_URI ) { - console.error( + throw new Error( "OAuth Error: One or more required environment variables (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_REDIRECT_URI) are not set.", ); }