created axiosInstance with refresh tokens

This commit is contained in:
lukew3 2021-05-14 21:09:22 -04:00
parent 40b978cb32
commit 0be490bf39
12 changed files with 167 additions and 41 deletions

View file

@ -14,15 +14,7 @@ sass.compiler = require("dart-sass");
let eslintConfig = {
parser: "babel-eslint",
globals: [
"jQuery",
"$",
"axios",
"Cookies",
"moment",
"html2canvas",
"ClipboardItem",
],
globals: ["jQuery", "$", "Cookies", "moment", "html2canvas", "ClipboardItem"],
envs: ["es6", "browser", "node"],
rules: {
"constructor-super": "error",
@ -89,6 +81,7 @@ let eslintConfig = {
//refactored files, which should be es6 modules
//once all files are moved here, then can we use a bundler to its full potential
const refactoredSrc = [
"./src/js/axios-instance.js",
"./src/js/db.js",
"./src/js/cloud-functions.js",
"./src/js/misc.js",

View file

@ -36,6 +36,7 @@ const userSchema = new Schema(
},
email: { type: String, required: true },
password: { type: String, required: true },
refreshTokens: [{ type: String, required: true }],
},
{
timestamps: true,
@ -46,4 +47,3 @@ const userSchema = new Schema(
const User = mongoose.model("User", userSchema);
module.exports = { User };
//export User;

View file

@ -66,6 +66,12 @@ app.post("/api/signIn", (req, res) => {
{ name: user.name },
process.env.ACCESS_TOKEN_SECRET
);
const refreshToken = jwt.sign(
{ name: user.name },
process.env.REFRESH_TOKEN_SECRET
);
user.refreshTokens.push(refreshToken);
user.save();
const retUser = {
uid: user._id,
displayName: user.name,
@ -73,7 +79,11 @@ app.post("/api/signIn", (req, res) => {
emailVerified: user.emailVerified,
metadata: { creationTime: user.createdAt },
};
res.json({ accessToken: accessToken, user: retUser });
res.json({
accessToken: accessToken,
refreshToken: refreshToken,
user: retUser,
});
} else {
//if password doesn't match hash
res.status(500).send({ error: "Password invalid" });
@ -111,6 +121,12 @@ app.post("/api/signUp", (req, res) => {
{ name: req.body.name },
process.env.ACCESS_TOKEN_SECRET
);
const refreshToken = jwt.sign(
{ name: user.name },
process.env.REFRESH_TOKEN_SECRET
);
user.refreshTokens.push(refreshToken);
user.save();
const retUser = {
uid: user._id,
displayName: user.name,
@ -118,7 +134,11 @@ app.post("/api/signUp", (req, res) => {
emailVerified: user.emailVerified,
metadata: { creationTime: user.createdAt },
};
res.json({ accessToken: accessToken, user: retUser });
res.json({
accessToken: accessToken,
refreshToken: refreshToken,
user: retUser,
});
})
.catch((e) => {
console.log(e);
@ -128,6 +148,23 @@ app.post("/api/signUp", (req, res) => {
});
});
app.post("/api/refreshToken", (req, res) => {
const authHeader = req.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];
if (token == null) return res.sendStatus(401);
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, identity) => {
if (err) return res.sendStatus(403);
User.findOne({ name: identity.name }, (err, user) => {
if (!user.refreshTokens.includes(token)) return res.sendStatus(403);
const accessToken = jwt.sign(
{ name: identity.name },
process.env.ACCESS_TOKEN_SECRET
);
res.json({ accessToken: accessToken });
});
});
});
app.post("/api/passwordReset", (req, res) => {
const email = req.body.email;
//send email to the passed email requesting password reset
@ -136,7 +173,6 @@ app.post("/api/passwordReset", (req, res) => {
app.get("/api/fetchSnapshot", authenticateToken, (req, res) => {
/* Takes token and returns snap */
//this is called in init snapshot
User.findOne({ name: req.name }, (err, user) => {
if (err) res.status(500).send({ error: err });
//populate snap object with data from user document
@ -147,6 +183,25 @@ app.get("/api/fetchSnapshot", authenticateToken, (req, res) => {
});
});
app.post("/api/testCompleted", authenticateToken, (req, res) => {
User.findOne({ name: req.name }, (err, user) => {
if (err) res.status(500).send({ error: err });
//Codes from legacy
//1 Saved: No personal best
//2 Saved: Personal best
//-1 Could not save result
//-2 Possible bot detected. Result not saved.
//-3 Could not verify keypress stats. Result not saved.
//-4 Result data does not make sense. Result not saved.
//-5 Test too short. Result not saved.
//-999 Internal error. Result might not be saved.
//return createdId
//return user data
res.json({ snap: snap });
});
});
app.get("/api/userResults", authenticateToken, (req, res) => {
User.findOne({ name: req.name }, (err, user) => {
if (err) res.status(500).send({ error: err });

View file

@ -13,16 +13,15 @@ import * as AllTimeStats from "./all-time-stats";
import * as DB from "./db";
import * as TestLogic from "./test-logic";
import * as UI from "./ui";
import axios from "axios";
import Cookies from "js-cookie";
import axiosInstance from "./axios-instance";
//var gmailProvider = new firebase.auth.GoogleAuthProvider();
export function signIn() {
$(".pageLogin .preloader").removeClass("hidden");
let email = $(".pageLogin .login input")[0].value;
let password = $(".pageLogin .login input")[1].value;
axios
axiosInstance
.post("/api/signIn", {
email: email,
password: password,
@ -32,12 +31,14 @@ export function signIn() {
if ($(".pageLogin .login #rememberMe input").prop("checked")) {
// TODO: set user login cookie that persists after session
Cookies.set("accessToken", response.data.accessToken);
Cookies.set("refreshToken", response.data.refreshToken);
Cookies.set("uid", response.data.user._id);
Cookies.set("displayName", response.data.user.name);
Cookies.set("email", response.data.user.email);
} else {
//set user login cookie to persist only as long as the session lives
Cookies.set("accessToken", response.data.accessToken);
Cookies.set("refreshToken", response.data.refreshToken);
Cookies.set("uid", response.data.user._id);
Cookies.set("displayName", response.data.user.name);
Cookies.set("email", response.data.user.email);
@ -133,7 +134,7 @@ function signUp() {
return;
}
axios
axiosInstance
.post("/api/signUp", {
name: nname,
email: email,
@ -142,6 +143,7 @@ function signUp() {
.then((response) => {
let usr = response.data.user;
Cookies.set("accessToken", response.data.accessToken);
Cookies.set("refreshToken", response.data.accessToken);
Cookies.set("uid", usr._id);
Cookies.set("displayName", usr.name);
Cookies.set("email", usr.email);
@ -184,7 +186,7 @@ function signUp() {
$(".pageLogin #forgotPasswordButton").click((e) => {
let email = prompt("Email address");
if (email) {
axios
axiosInstance
.post("/api/passwordReset", {
email: email,
})

65
src/js/axios-instance.js Normal file
View file

@ -0,0 +1,65 @@
import Cookies from "js-cookie";
import axios from "axios";
const axiosInstance = axios.create();
// Request interceptor for API calls
axiosInstance.interceptors.request.use(
async (config) => {
const accessToken = Cookies.get("accessToken")
? Cookies.get("accessToken")
: null;
if (accessToken) {
config.headers = {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"Content-Type": "application/json",
};
} else {
config.headers = {
Accept: "application/json",
"Content-Type": "application/json",
};
}
return config;
},
(error) => {
Promise.reject(error);
}
);
// Response interceptor for API calls
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
async function (error) {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
//console.log("Refreshing access token");
const refreshToken = Cookies.get("refreshToken")
? Cookies.get("refreshToken")
: null;
await axios
.post(
`/api/refreshToken`,
{},
{ headers: { Authorization: `Bearer ${refreshToken}` } }
)
.then((response) => {
Cookies.set("accessToken", response.data.accessToken);
axios.defaults.headers.common["Authorization"] =
"Bearer " + response.data.accessToken;
})
.catch((error) => {
console.log(error);
axios.defaults.headers.common["Authorization"] = "Bearer failed";
});
return axiosInstance(originalRequest);
}
return Promise.reject(error);
}
);
export default axiosInstance;

View file

@ -2,7 +2,7 @@
//export function testCompleted = axios.post('/api/testCompleted', )firebase
// .functions()
// .httpsCallable("testCompleted");
import axios from "axios";
import axiosInstance from "./axios-instance";
export function testCompleted(input) {
console.log("testCompleted");

View file

@ -4,7 +4,7 @@ import Config, * as UpdateConfig from "./config";
import * as Focus from "./focus";
import * as CommandlineLists from "./commandline-lists";
import * as TestUI from "./test-ui";
import axios from "axios";
import axiosInstance from "./axios-instance";
let commandLineMouseMode = false;
@ -164,7 +164,7 @@ function trigger(command) {
}
});
if (!subgroup && !input && !sticky) {
axios
axiosInstance
.post("/api/analytics/usedCommandLine", { command: command })
.catch(() => {
console.log("Analytics unavailable");
@ -324,7 +324,7 @@ $("#commandInput input").keydown((e) => {
}
}
});
axios
axiosInstance
.post("/api/analytics/usedCommandLine", { command: command })
.catch(() => {
console.log("Analytics unavailable");

View file

@ -17,7 +17,7 @@ import * as UI from "./ui";
import * as CommandlineLists from "./commandline-lists";
import * as BackgroundFilter from "./custom-background-filter";
import LayoutList from "./layouts";
import axios from "axios";
import axiosInstance from "./axios-instance";
export let localStorageConfig = null;
export let dbConfigLoaded = false;
@ -1218,7 +1218,7 @@ export function setLanguage(language, nosave) {
language = "english";
}
config.language = language;
axios
axiosInstance
.post("/api/analytics/changedLanguage", { language: language })
.catch(() => {
console.log("Analytics unavailable");

View file

@ -2,7 +2,7 @@ import { loadTags } from "./result-filters";
import * as AccountButton from "./account-button";
import * as CloudFunctions from "./cloud-functions";
import * as Notifications from "./notifications";
import axios from "axios";
import axiosInstance from "./axios-instance";
import Cookies from "js-cookie";
//const db = firebase.firestore();
@ -14,7 +14,7 @@ let dbSnapshot = null;
export function updateName(uid, name) {
//db.collection(`users`).doc(uid).set({ name: name }, { merge: true });
axios.post("/api/updateName", {
axiosInstance.post("/api/updateName", {
uid: uid,
name: name,
});
@ -52,10 +52,8 @@ export async function initSnapshot() {
//send api request with token that returns tags, presets, and data needed for snap
if (currentUser() == null) return false;
const token = Cookies.get("accessToken");
await axios
.get("/api/fetchSnapshot", {
headers: { Authorization: `Bearer ${token}` },
})
await axiosInstance
.get("/api/fetchSnapshot")
.then((response) => {
dbSnapshot = response.data.snap;
loadTags(dbSnapshot.tags);
@ -73,7 +71,7 @@ export async function getUserResults() {
if (dbSnapshot.results !== undefined) {
return true;
} else {
axios
axiosInstance
.get("/api/userResults", {
uid: user.uid,
})
@ -435,7 +433,7 @@ export function updateLbMemory(mode, mode2, type, value) {
export async function saveConfig(config) {
if (currentUser() !== null) {
AccountButton.loading(true);
axios
axiosInstance
.post("/api/saveConfig", {
uid: currentUser().uid,
})

View file

@ -1,6 +1,6 @@
import * as Loader from "./loader";
import * as DB from "./db";
import axios from "axios";
import axiosInstance from "./axios-instance";
export function getuid() {
console.error("Only share this uid with Miodec and nobody else!");
@ -316,7 +316,7 @@ export function migrateFromCookies() {
export function sendVerificationEmail() {
Loader.show();
let cu = DB.currentUser();
axios
axiosInstance
.post("/api/sendEmailVerification", {
uid: cu.uid,
})

View file

@ -28,6 +28,7 @@ import * as ThemeColors from "./theme-colors";
import * as CloudFunctions from "./cloud-functions";
import * as TestLeaderboards from "./test-leaderboards";
import * as Replay from "./replay.js";
import axiosInstance from "./axios-instance";
export let notSignedInLastResult = null;
@ -311,9 +312,9 @@ export function startTest() {
}
try {
if (DB.currentUser() != null) {
axios.post("/api/analytics/testStarted");
axiosInstance.post("/api/analytics/testStarted");
} else {
axios.post("/api/analytics/testStartedNoLogin");
axiosInstance.post("/api/analytics/testStartedNoLogin");
}
} catch (e) {
console.log("Analytics unavailable");
@ -1541,6 +1542,18 @@ export function finish(difficultyFailed = false) {
`checking <i class="fas fa-spin fa-fw fa-circle-notch"></i>`
);
}
const token = Cookies.get("accessToken");
axiosInstance
.post("/api/testCompleted", {
obj: completedEvent,
})
.then((response) => {
//return a result message that will be shown if there was an error
})
.catch((error) => {
Notifications.add(error, -1);
});
CloudFunctions.testCompleted({
uid: DB.currentUser().uid,
obj: completedEvent,
@ -1616,7 +1629,7 @@ export function finish(difficultyFailed = false) {
}
}
axios
axiosInstance
.post("/api/analytics/testCompleted", {
completedEvent: completedEvent,
})
@ -1657,7 +1670,7 @@ export function finish(difficultyFailed = false) {
});
});
} else {
axios
axiosInstance
.post("/api/analytics/testCompletedNoLogin", {
completedEvent: completedEvent,
})
@ -1669,7 +1682,7 @@ export function finish(difficultyFailed = false) {
} else {
Notifications.add("Test invalid", 0);
TestStats.setInvalid();
axios
axiosInstance
.post("/api/analytics/testCompletedInvalid", {
completedEvent: completedEvent,
})

View file

@ -5,7 +5,7 @@ import * as Notifications from "./notifications";
import Config from "./config";
import * as UI from "./ui";
import tinycolor from "tinycolor2";
import axios from "axios";
import axiosInstance from "./axios-instance";
let isPreviewingTheme = false;
export let randomTheme = null;
@ -96,7 +96,7 @@ export function apply(themeName) {
});
}
axios
axiosInstance
.post("/api/analytics/changedTheme", {
theme: themeName,
})