mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-30 02:39:05 +08:00
created axiosInstance with refresh tokens
This commit is contained in:
parent
40b978cb32
commit
0be490bf39
12 changed files with 167 additions and 41 deletions
11
gulpfile.js
11
gulpfile.js
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
61
server.js
61
server.js
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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
65
src/js/axios-instance.js
Normal 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;
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
14
src/js/db.js
14
src/js/db.js
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue