diff --git a/DiscordBot/commands/ban.js b/DiscordBot/commands/ban.js index b466373..793869b 100644 --- a/DiscordBot/commands/ban.js +++ b/DiscordBot/commands/ban.js @@ -1,4 +1,5 @@ const User = require("../../model/user.js"); +const functions = require("../../structs/functions.js"); const fs = require("fs"); const config = JSON.parse(fs.readFileSync("./Config/config.json").toString()); @@ -39,6 +40,8 @@ module.exports = { if (xmppClient) xmppClient.client.close(); } + if (accessToken != -1 || refreshToken != -1) functions.UpdateTokens(); + interaction.editReply({ content: `Successfully banned ${targetUser.username}`, ephemeral: true }); } } \ No newline at end of file diff --git a/DiscordBot/commands/kick.js b/DiscordBot/commands/kick.js index e0d2ff4..2468271 100644 --- a/DiscordBot/commands/kick.js +++ b/DiscordBot/commands/kick.js @@ -1,4 +1,5 @@ const User = require("../../model/user.js"); +const functions = require("../../structs/functions.js"); const fs = require("fs"); const config = JSON.parse(fs.readFileSync("./Config/config.json").toString()); @@ -36,7 +37,11 @@ module.exports = { if (xmppClient) xmppClient.client.close(); } - if (accessToken != -1 || refreshToken != -1) return interaction.editReply({ content: `Successfully kicked ${targetUser.username}`, ephemeral: true }); + if (accessToken != -1 || refreshToken != -1) { + functions.UpdateTokens(); + + return interaction.editReply({ content: `Successfully kicked ${targetUser.username}`, ephemeral: true }); + } interaction.editReply({ content: `There are no current active sessions by ${targetUser.username}`, ephemeral: true }); } diff --git a/DiscordBot/commands/sign-out-of-all-sessions.js b/DiscordBot/commands/sign-out-of-all-sessions.js index e3d65d4..b6ed85f 100644 --- a/DiscordBot/commands/sign-out-of-all-sessions.js +++ b/DiscordBot/commands/sign-out-of-all-sessions.js @@ -1,4 +1,5 @@ const User = require("../../model/user.js"); +const functions = require("../../structs/functions.js"); module.exports = { commandInfo: { @@ -22,7 +23,11 @@ module.exports = { if (xmppClient) xmppClient.client.close(); } - if (accessToken != -1 || refreshToken != -1) return interaction.editReply({ content: `Successfully signed out of all sessions!`, ephemeral: true }); + if (accessToken != -1 || refreshToken != -1) { + functions.UpdateTokens(); + + return interaction.editReply({ content: `Successfully signed out of all sessions!`, ephemeral: true }); + } interaction.editReply({ content: `You have no current active sessions.`, ephemeral: true }); } diff --git a/index.js b/index.js index e36362c..141cf29 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ const app = express(); const mongoose = require("mongoose"); const fs = require("fs"); const rateLimit = require("express-rate-limit"); +const jwt = require("jsonwebtoken"); const config = JSON.parse(fs.readFileSync("./Config/config.json").toString()); const log = require("./structs/log.js"); @@ -14,9 +15,23 @@ if (!fs.existsSync("./ClientSettings")) fs.mkdirSync("./ClientSettings"); global.JWT_SECRET = functions.MakeID(); const PORT = 8080; -global.accessTokens = []; -global.refreshTokens = []; -global.clientTokens = []; +const tokens = JSON.parse(fs.readFileSync("./tokenManager/tokens.json").toString()); + +for (let tokenType in tokens) { + for (let tokenIndex in tokens[tokenType]) { + let decodedToken = jwt.decode(tokens[tokenType][tokenIndex].token.replace("eg1~", "")); + + if (DateAddHours(new Date(decodedToken.creation_date), decodedToken.hours_expire).getTime() <= new Date().getTime()) { + tokens[tokenType].splice(Number(tokenIndex), 1); + } + } +} + +fs.writeFileSync("./tokenManager/tokens.json", JSON.stringify(tokens, null, 2)); + +global.accessTokens = tokens.accessTokens; +global.refreshTokens = tokens.refreshTokens; +global.clientTokens = tokens.clientTokens; global.exchangeCodes = []; @@ -58,3 +73,10 @@ app.use((req, res, next) => { undefined, 1004, undefined, 404, res ); }); + +function DateAddHours(pdate, number) { + let date = pdate; + date.setHours(date.getHours() + number); + + return date; +} \ No newline at end of file diff --git a/routes/auth.js b/routes/auth.js index cf9e296..425c7a0 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -35,6 +35,9 @@ app.post("/account/api/oauth/token", async (req, res) => { if (clientToken != -1) global.clientTokens.splice(clientToken, 1); const token = tokenCreation.createClient(clientId, req.body.grant_type, ip, 4); // expires in 4 hours + + functions.UpdateTokens(); + const decodedClient = jwt.decode(token); res.json({ @@ -84,10 +87,17 @@ app.post("/account/api/oauth/token", async (req, res) => { try { if (refreshToken == -1) throw new Error("Refresh token invalid."); + let decodedRefreshToken = jwt.decode(refresh_token.replace("eg1~", "")); - jwt.verify(refresh_token.replace("eg1~", ""), global.JWT_SECRET); + if (DateAddHours(new Date(decodedRefreshToken.creation_date), decodedRefreshToken.hours_expire).getTime() <= new Date().getTime()) { + throw new Error("Expired refresh token."); + } } catch { - if (refreshToken != -1) global.refreshTokens.splice(refreshToken, 1); + if (refreshToken != -1) { + global.refreshTokens.splice(refreshToken, 1); + + functions.UpdateTokens(); + } error.createError( "errors.com.epicgames.account.auth_token.invalid_refresh_token", @@ -154,6 +164,8 @@ app.post("/account/api/oauth/token", async (req, res) => { const accessToken = tokenCreation.createAccess(req.user, clientId, req.body.grant_type, deviceId, 8); // expires in 8 hours const refreshToken = tokenCreation.createRefresh(req.user, clientId, req.body.grant_type, deviceId, 24); // expires in 24 hours + functions.UpdateTokens(); + const decodedAccess = jwt.decode(accessToken); const decodedRefresh = jwt.decode(refreshToken); @@ -252,6 +264,8 @@ app.delete("/account/api/oauth/sessions/kill/:token", (req, res) => { let clientIndex = global.clientTokens.findIndex(i => i.token == token); if (clientIndex != -1) global.clientTokens.splice(clientIndex, 1); + if (accessIndex != -1 || clientIndex != -1) functions.UpdateTokens(); + res.status(204).end(); }); diff --git a/routes/user.js b/routes/user.js index 4998d8e..576d938 100644 --- a/routes/user.js +++ b/routes/user.js @@ -56,7 +56,7 @@ app.get("/account/api/public/account/displayName/:displayName", async (req, res) }); app.get("/persona/api/public/account/lookup", async (req, res) => { - if (typeof req.query.q != "string") return error.createError( + if (typeof req.query.q != "string" || !req.query.q) return error.createError( "errors.com.epicgames.bad_request", "Required String parameter 'q' is invalid or not present", undefined, 1001, undefined, 400, res @@ -79,7 +79,7 @@ app.get("/persona/api/public/account/lookup", async (req, res) => { app.get("/api/v1/search/:accountId", async (req, res) => { let response = []; - if (typeof req.query.prefix != "string") return error.createError( + if (typeof req.query.prefix != "string" || !req.query.prefix) return error.createError( "errors.com.epicgames.bad_request", "Required String parameter 'prefix' is invalid or not present", undefined, 1001, undefined, 400, res diff --git a/structs/functions.js b/structs/functions.js index 4b544da..f2f9ed3 100644 --- a/structs/functions.js +++ b/structs/functions.js @@ -301,6 +301,14 @@ function DecodeBase64(str) { return Buffer.from(str, 'base64').toString(); } +function UpdateTokens() { + fs.writeFileSync("./tokenManager/tokens.json", JSON.stringify({ + accessTokens: global.accessTokens, + refreshTokens: global.refreshTokens, + clientTokens: global.clientTokens + }, null, 2)); +} + module.exports = { sleep, GetVersionInfo, @@ -312,5 +320,6 @@ module.exports = { sendXmppMessageToId, getPresenceFromUser, registerUser, - DecodeBase64 + DecodeBase64, + UpdateTokens } \ No newline at end of file diff --git a/tokenManager/tokenVerify.js b/tokenManager/tokenVerify.js index 23864b3..11201f6 100644 --- a/tokenManager/tokenVerify.js +++ b/tokenManager/tokenVerify.js @@ -1,6 +1,7 @@ const jwt = require("jsonwebtoken"); const User = require("../model/user.js"); +const functions = require("../structs/functions.js"); const error = require("../structs/error.js"); async function verifyToken(req, res, next) { @@ -15,10 +16,14 @@ async function verifyToken(req, res, next) { const token = req.headers["authorization"].replace("bearer eg1~", ""); try { - const decodedToken = jwt.verify(token, global.JWT_SECRET); + const decodedToken = jwt.decode(token); if (!global.accessTokens.find(i => i.token == `eg1~${token}`)) throw new Error("Invalid token."); + if (DateAddHours(new Date(decodedToken.creation_date), decodedToken.hours_expire).getTime() <= new Date().getTime()) { + throw new Error("Expired access token."); + } + req.user = await User.findOne({ accountId: decodedToken.sub }).lean(); if (req.user.banned) return error.createError( @@ -30,7 +35,11 @@ async function verifyToken(req, res, next) { next(); } catch { let accessIndex = global.accessTokens.findIndex(i => i.token == `eg1~${token}`); - if (accessIndex != -1) global.accessTokens.splice(accessIndex, 1); + if (accessIndex != -1) { + global.accessTokens.splice(accessIndex, 1); + + functions.UpdateTokens(); + } return authErr(); } @@ -48,12 +57,16 @@ async function verifyClient(req, res, next) { const token = req.headers["authorization"].replace("bearer eg1~", ""); try { - const decodedToken = jwt.verify(token, global.JWT_SECRET); + const decodedToken = jwt.decode(token); let findAccess = global.accessTokens.find(i => i.token == `eg1~${token}`); if (!findAccess && !global.clientTokens.find(i => i.token == `eg1~${token}`)) throw new Error("Invalid token."); + if (DateAddHours(new Date(decodedToken.creation_date), decodedToken.hours_expire).getTime() <= new Date().getTime()) { + throw new Error("Expired access/client token."); + } + if (findAccess) { req.user = await User.findOne({ accountId: decodedToken.sub }).lean(); @@ -71,11 +84,20 @@ async function verifyClient(req, res, next) { let clientIndex = global.clientTokens.findIndex(i => i.token == `eg1~${token}`); if (clientIndex != -1) global.clientTokens.splice(clientIndex, 1); + + if (accessIndex != -1 || clientIndex != -1) functions.UpdateTokens(); return authErr(); } } +function DateAddHours(pdate, number) { + let date = pdate; + date.setHours(date.getHours() + number); + + return date; +} + module.exports = { verifyToken, verifyClient diff --git a/tokenManager/tokens.json b/tokenManager/tokens.json new file mode 100644 index 0000000..3dd8d5f --- /dev/null +++ b/tokenManager/tokens.json @@ -0,0 +1,5 @@ +{ + "accessTokens": [], + "refreshTokens": [], + "clientTokens": [] +} \ No newline at end of file