This PR mainly refactors the code by extracting a "core" package which is then used by the "backend" and "frontend" apps. Apart from this the PR also contains several smaller changes like fixing dependabot, upgrading dependencies, aligned "package.json" files, added tests, ... addresses step 1 of #32 closes #136 --------- Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
106 lines
3.3 KiB
JavaScript
106 lines
3.3 KiB
JavaScript
import axios from "axios";
|
|
|
|
import { storeUser } from "./common/database.js";
|
|
|
|
/**
|
|
* Given an access token, return the GitHub login (userId) or null if invalid
|
|
*
|
|
* @param {string} accessToken GitHub access token
|
|
* @returns {Promise<string|null>} login name or null if invalid access_token
|
|
*/
|
|
async function getUserFromToken(accessToken) {
|
|
const res = await axios.get("https://api.github.com/user", {
|
|
headers: {
|
|
Accept: "application/vnd.github.v3+json",
|
|
Authorization: `bearer ${accessToken}`,
|
|
},
|
|
});
|
|
|
|
return res.data && res.data.login ? res.data.login : null;
|
|
}
|
|
|
|
/**
|
|
* Exchanges OAuth code for access token and returns userId + accessToken
|
|
*
|
|
* @param {string} code GitHub authentication code from OAuth process
|
|
* @param privateAccess whether private access was requested
|
|
* @returns {Promise<{userId: string, accessToken: string, needDowngrade: boolean}>} user_id and access_token of authenticated user, and whether downgrade is needed
|
|
*/
|
|
async function githubAuthenticate(code, privateAccess) {
|
|
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.",
|
|
);
|
|
}
|
|
|
|
const start = Date.now();
|
|
const params = new URLSearchParams({
|
|
client_id: process.env.OAUTH_CLIENT_ID,
|
|
client_secret: process.env.OAUTH_CLIENT_SECRET,
|
|
code,
|
|
redirect_uri: process.env.OAUTH_REDIRECT_URI,
|
|
});
|
|
|
|
try {
|
|
const res = await axios.post(
|
|
"https://github.com/login/oauth/access_token",
|
|
params.toString(),
|
|
{
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
Accept: "application/json",
|
|
},
|
|
},
|
|
);
|
|
|
|
const body = res.data;
|
|
const accessToken = body && body.access_token ? body.access_token : null;
|
|
|
|
if (!accessToken) {
|
|
throw new Error(
|
|
"OAuth Error: access_token missing from response: " +
|
|
JSON.stringify(body),
|
|
);
|
|
}
|
|
|
|
const userId = await getUserFromToken(accessToken);
|
|
|
|
if (!userId) {
|
|
throw new Error("OAuth Error: Invalid user_id/access_token");
|
|
}
|
|
|
|
// if user previously granted private access, then logged in via public flow
|
|
const needDowngrade = body.scope && !privateAccess;
|
|
|
|
console.log("GitHub Authentication", `${Date.now() - start} ms`);
|
|
return { userId, accessToken, needDowngrade };
|
|
} catch (err) {
|
|
if (err.response) {
|
|
// eslint-disable-next-line preserve-caught-error
|
|
throw new Error(`OAuth Error: ${err.response.status}`);
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Authenticate using the OAuth code and update DB with associated user info.
|
|
*
|
|
* @param {string} code GitHub authentication code from OAuth process
|
|
* @param {boolean} privateAccess whether private access was requested
|
|
* @param {string} userKey user key to associate with the user
|
|
* @returns {Promise<{userId: string, needDowngrade: boolean}>} user_id of authenticated user and whether downgrade is needed
|
|
*/
|
|
export async function authenticate(code, privateAccess, userKey) {
|
|
const { userId, accessToken, needDowngrade } = await githubAuthenticate(
|
|
code,
|
|
privateAccess,
|
|
);
|
|
await storeUser(userId, accessToken, userKey, needDowngrade || privateAccess);
|
|
return { userId, needDowngrade };
|
|
}
|